diff --git a/.env.example b/.env.example
index d895fdb..faabc64 100644
--- a/.env.example
+++ b/.env.example
@@ -12,10 +12,12 @@ EXPO_PUBLIC_CONVEX_URL=https://example.convex.cloud
EXPO_PUBLIC_CONVEX_SITE_URL=https://example.convex.site
# AgentOS / Rivet runtime
-# RIVET_ENDPOINT is the private Engine control plane. RIVET_SERVERLESS_ENDPOINT
-# is the callback path where that Engine reaches the registry inside Flue.
+# The native Mode A envoy connects outbound to this private Engine control plane.
+# Convex calls authenticated Hono endpoints; it never connects to the Engine.
RIVET_ENDPOINT=http://localhost:6420
-RIVET_SERVERLESS_ENDPOINT=http://127.0.0.1:3000/internal/rivet
+RIVETKIT_RUNTIME_MODE=envoy
+RIVETKIT_RUNTIME=native
+RIVET_ENVOY_VERSION=1
RIVET_WORKSPACE_TOKEN=replace-with-a-long-random-workspace-token
AGENT_WORKSPACE_ROOT=/absolute/path/to/zopu-agent-workspaces
diff --git a/apps/web/src/components/projects/project-setup-panel.tsx b/apps/web/src/components/projects/project-setup-panel.tsx
index d918c16..6013040 100644
--- a/apps/web/src/components/projects/project-setup-panel.tsx
+++ b/apps/web/src/components/projects/project-setup-panel.tsx
@@ -8,12 +8,6 @@ import { useProjectSetup } from "@/hooks/use-project-setup";
import { ProjectSetupStatus } from "./project-setup-status";
-const handleRetry = () => {
- // The setup query is reactive — a retry on the backend will append new
- // events and the phase derivation updates automatically. No client-side
- // refetch is needed; this is a no-op signal for assistive tech.
-};
-
export interface ProjectSetupPanelProps {
readonly onClose: () => void;
readonly onEnterProject: () => void;
@@ -80,7 +74,10 @@ export const ProjectSetupPanel = ({
) : (
-
+
)}
{setup.kind === "ready" && setup.phase === "ready" ? (
diff --git a/apps/web/src/hooks/use-project-setup.ts b/apps/web/src/hooks/use-project-setup.ts
index 79918ae..edabafe 100644
--- a/apps/web/src/hooks/use-project-setup.ts
+++ b/apps/web/src/hooks/use-project-setup.ts
@@ -1,6 +1,7 @@
import type { Id } from "@code/backend/convex/_generated/dataModel";
-import { useQuery } from "convex/react";
+import { useAction, useQuery } from "convex/react";
import { makeFunctionReference } from "convex/server";
+import { useState } from "react";
/**
* Setup event types emitted by the backend project-setup coordinator. The
@@ -43,18 +44,41 @@ export interface ProjectSetupEvent {
}
export interface ProjectSetupState {
+ readonly attempt: number;
readonly events: readonly ProjectSetupEvent[];
readonly phase: ProjectSetupPhase;
+ readonly retry: () => Promise;
+ readonly retrying: boolean;
}
export type ProjectSetupResult =
| { readonly kind: "loading" }
| ({ readonly kind: "ready" } & ProjectSetupState);
+/**
+ * Status of a project's durable runtime row, as returned by
+ * `projects.getSetup`. This is the source of truth for UI phase derivation: a
+ * stale `project.setup.failed` event from a prior attempt must NOT keep the UI
+ * blocked once the runtime has been retried into a non-terminal status.
+ */
+type RuntimeStatus =
+ | "requested"
+ | "creating_vm"
+ | "cloning"
+ | "checking_repository"
+ | "ready"
+ | "failed";
+
+interface RuntimeRow {
+ readonly attempt: number;
+ readonly status: RuntimeStatus;
+}
+
/**
* Minimal structural shape of the `projects.getSetup` return value. We define
* this locally rather than importing from generated code so the hook compiles
- * before the backend regenerates its API manifest.
+ * before the backend regenerates its API manifest. The `runtime` field is the
+ * durable project runtime row (or null before the row exists).
*/
interface SetupQueryResult {
readonly events: readonly {
@@ -62,6 +86,15 @@ interface SetupQueryResult {
readonly payload: unknown;
readonly type: ProjectSetupEventType;
}[];
+ readonly runtime: {
+ readonly attempt: number;
+ readonly status: RuntimeStatus;
+ } | null;
+}
+
+interface RetrySetupResult {
+ readonly attempt: number;
+ readonly retried: boolean;
}
const getSetupRef = makeFunctionReference<
@@ -70,6 +103,19 @@ const getSetupRef = makeFunctionReference<
SetupQueryResult
>("projects:getSetup");
+/**
+ * Authenticated retry endpoint. Resets a failed runtime to a non-terminal
+ * status, increments the attempt, and reschedules the existing
+ * `projectSetup:runSetup` coordinator. Returns `{ retried, attempt }`; a
+ * `retried: false` result means the runtime was not failed (e.g. already
+ * ready, or still in-progress) and nothing was re-run.
+ */
+const retrySetupRef = makeFunctionReference<
+ "action",
+ { projectId: Id<"projects"> },
+ RetrySetupResult
+>("projects:retrySetup");
+
const ORDERED_PHASES: readonly ProjectSetupPhase[] = [
"creating_workspace",
"cloning_repository",
@@ -97,28 +143,34 @@ const typeToPhase = (type: ProjectSetupEventType): ProjectSetupPhase => {
}
};
+const RUNTIME_STATUS_TO_PHASE: Record = {
+ checking_repository: "checking_repository",
+ cloning: "cloning_repository",
+ creating_vm: "creating_workspace",
+ failed: "blocked",
+ ready: "ready",
+ requested: "creating_workspace",
+};
+
/**
- * Derive the user-facing phase from the raw event stream.
+ * Derive the user-facing phase.
*
- * - A `blocked` event always wins (terminal error state).
- * - A `ready` event wins over any in-progress phase.
- * - Otherwise we map observed events to the furthest in-progress phase.
+ * The durable runtime row is the source of truth. Only when no runtime row
+ * exists yet (the very first import, before the coordinator has persisted a
+ * row) do we fall back to the event stream. A historical `project.setup.failed`
+ * event from a prior attempt never blocks the UI once the current runtime is
+ * non-failed — we never consult events for terminality when a runtime exists.
*/
const derivePhase = (
- events: readonly ProjectSetupEvent[]
+ events: readonly ProjectSetupEvent[],
+ runtime: RuntimeRow | null
): ProjectSetupPhase => {
- if (events.length === 0) {
- return "creating_workspace";
+ if (runtime) {
+ return RUNTIME_STATUS_TO_PHASE[runtime.status];
}
- const hasBlocked = events.some(
- (event) =>
- event.type === "project.setup.failed" ||
- event.type === "project.setup.blocked" ||
- event.type === "project.preview.blocked"
- );
- if (hasBlocked) {
- return "blocked";
+ if (events.length === 0) {
+ return "creating_workspace";
}
const hasReady = events.some(
@@ -142,9 +194,15 @@ const derivePhase = (
};
/**
- * Subscribe to a project's setup lifecycle via the `projects.getSetup` query.
- * Returns a loading sentinel while the first fetch is in flight, then a
- * derived `{ phase, events }` state that components can render directly.
+ * Subscribe to a project's setup lifecycle via the `projects.getSetup` query,
+ * and expose an authenticated retry control bound to `projects:retrySetup`.
+ *
+ * Returns a loading sentinel while the first fetch is in flight, then a derived
+ * `{ phase, events, attempt, retry, retrying }` state. Phase is derived from
+ * the durable runtime row (the source of truth), so a historical failed event
+ * cannot keep the UI blocked once the runtime has been retried. Components
+ * never receive raw backend errors — `retry` resolves cleanly and surfaces
+ * progress via the reactive query once the coordinator reschedules.
*/
export const useProjectSetup = (
projectId: Id<"projects"> | undefined
@@ -153,8 +211,10 @@ export const useProjectSetup = (
getSetupRef,
projectId === undefined ? "skip" : { projectId }
);
+ const retrySetup = useAction(retrySetupRef);
+ const [retrying, setRetrying] = useState(false);
- if (setup === undefined) {
+ if (projectId === undefined || setup === undefined) {
return { kind: "loading" };
}
@@ -163,6 +223,36 @@ export const useProjectSetup = (
payload: event.payload,
type: event.type,
}));
- const phase = derivePhase(events);
- return { events, kind: "ready", phase };
+ const runtime: RuntimeRow | null = setup.runtime
+ ? { attempt: setup.runtime.attempt, status: setup.runtime.status }
+ : null;
+ const attempt = runtime?.attempt ?? 1;
+
+ // While a retry is in flight (between the user click and the reactive
+ // runtime-status update landing), force an in-progress phase so the UI never
+ // lingers on the stale blocked state from the prior failed attempt.
+ const basePhase = derivePhase(events, runtime);
+ const phase: ProjectSetupPhase =
+ retrying && basePhase === "blocked" ? "creating_workspace" : basePhase;
+
+ const retry = async (): Promise => {
+ if (projectId === undefined || retrying) {
+ return;
+ }
+ // The retry endpoint is idempotent and self-scoping: it only re-runs when
+ // the runtime is failed. Swallow rejections so raw backend errors never
+ // reach the UI; progress is observed reactively through getSetup once the
+ // coordinator reschedules (runtime status flips to non-terminal).
+ setRetrying(true);
+ try {
+ await retrySetup({ projectId });
+ } catch {
+ // No user-visible raw error; the reactive query keeps the UI honest
+ // about the true runtime state.
+ } finally {
+ setRetrying(false);
+ }
+ };
+
+ return { attempt, events, kind: "ready", phase, retry, retrying };
};
diff --git a/deploy/vds/Caddyfile b/deploy/vds/Caddyfile
index 1f3e355..2637a10 100644
--- a/deploy/vds/Caddyfile
+++ b/deploy/vds/Caddyfile
@@ -13,10 +13,9 @@
# /health liveness probe
# /internal/project-setup single-call project setup coordinator
# /internal/agents//events agent event dispatch callback
-# - The RivetKit serverless registry at /internal/rivet/* is NOT proxied. It
-# is a private loopback callback the Engine reaches directly at
-# http://127.0.0.1:3000/internal/rivet; exposing it publicly would widen the
-# private worker protocol.
+# - The AgentOS registry runs in-process as a native envoy (Rivet Mode A),
+# so there is no /internal/rivet/* route to expose. Exposing a private
+# worker protocol publicly would widen the attack surface needlessly.
# - Former routes (/agents/zopu/*, /internal/work-attempts/*, /workflows/*,
# /api/rivet/*) are intentionally absent: they are stale and not part of the
# current Convex→agents contract.
diff --git a/deploy/vds/README.md b/deploy/vds/README.md
index ceaf142..dc05eb7 100644
--- a/deploy/vds/README.md
+++ b/deploy/vds/README.md
@@ -1,36 +1,35 @@
# Zopu VDS agents deployment
-This directory holds the systemd unit, environment template, and Caddy reverse-proxy config that run the single Zopu agents Node process on the VDS. It supersedes the former container deployment for the agents process.
+This directory holds the systemd unit, environment template, and Caddy reverse-proxy config that run the Zopu intelligence runtime on the VDS. It supersedes the former container deployment for the agents process.
## Runtime topology
-One Node process (`zopu-agents.service`) runs the whole intelligence runtime:
+One Node process hosts the entire runtime:
```
zopu-agents.service
└─ node /srv/zopu/current/packages/agents/dist/server.mjs
├─ Hono server 127.0.0.1:3000
├─ Flue 2.0 agents SQLite at /srv/zopu/data/flue/flue.db
- └─ AgentOS registry in-process, serverless, at /internal/rivet/*
+ └─ AgentOS registry envoy, native runtime (in-process)
```
-The **Rivet Engine is a separate private service** on `127.0.0.1:6420`, managed by its own unit/compose. This unit does not start it. There is **no standalone AgentOS runner**: the registry handler runs inside the agents process.
+The process connects outbound to the **Rivet Engine**, a separate private service on `127.0.0.1:6420`, managed by its own unit/compose. This unit does not start or own it.
-### The two Rivet endpoints (not interchangeable)
+The Hono/Flue server handles Convex callbacks and dispatches to Flue agents. The **AgentOS actor is hosted in-process**: the same process is the envoy that owns the native Actor Runtime Socket (Rivet Mode A). There is no separate runner process and no Rivet HTTP handler route.
+
+### Rivet endpoint
| Variable | Role | Value on this host |
| --- | --- | --- |
| `RIVET_ENDPOINT` | Engine **control plane** this process connects _to_ for actor metadata | `http://127.0.0.1:6420` |
-| `RIVET_SERVERLESS_ENDPOINT` | Address the engine calls **back into** this process's `/internal/rivet` handler | `http://127.0.0.1:3000/internal/rivet` |
-
-Pointing both at the engine, or dropping the `/internal/rivet` path, breaks the registry. See `agents.env.example` for the authoritative comments.
## Files
-- `zopu-agents.service` — systemd unit. Runs `/srv/zopu/current/.../server.mjs` as user `zopu`, waits for engine health before binding, hardens the service, and restarts on failure.
-- `agents.env.example` — template for `/etc/zopu/agents.env`. Contains the full validated env schema with no secrets.
+- `zopu-agents.service` — systemd unit. Runs `/srv/zopu/current/packages/agents/dist/server.mjs` as user `zopu`, waits for engine health before binding, hardens the service, and restarts on failure.
+- `agents.env.example` — template for `/etc/zopu/agents.env`. Contains the full validated env schema (Hono/Flue + in-process envoy) with no secrets.
- `../../scripts/release-agents.sh` — local build, upload, and atomic release promotion command. Each release includes this directory so the unit's `Documentation=` target remains valid.
-- `Caddyfile` — source-controlled public ingress for `agents.zopu.puter.wtf`. Terminates TLS and forwards only `/health`, `/internal/project-setup`, and `/internal/agents/*` to `127.0.0.1:3000`; the private `/internal/rivet/*` registry handler is never proxied.
+- `Caddyfile` — source-controlled public ingress for `agents.zopu.puter.wtf`. Terminates TLS and forwards only `/health`, `/internal/project-setup`, and `/internal/agents/*` to `127.0.0.1:3000`.
## Prerequisites on the VDS
@@ -73,7 +72,7 @@ curl -fsS http://127.0.0.1:3000/health # {"service":"zopu-agents","status
### Public ingress (Caddy)
-Caddy terminates TLS for `agents.zopu.puter.wtf` and forwards only the three paths the Convex backend calls to the internal agents worker (`127.0.0.1:3000`). The in-process serverless registry at `/internal/rivet/*` is reachable only over loopback (the Engine's `RIVET_SERVERLESS_ENDPOINT` callback), never through Caddy. All other paths return `404`.
+Caddy terminates TLS for `agents.zopu.puter.wtf` and forwards only the three paths the Convex backend calls to the internal agents worker (`127.0.0.1:3000`). The AgentOS actor registry runs in-process, so no `/internal/rivet/*` path is exposed on the agents listener. All other paths return `404`.
Install or update from this checked-in artifact:
@@ -92,7 +91,6 @@ Verify the narrow public surface:
```sh
curl -fsS https://agents.zopu.puter.wtf/health # {"service":"zopu-agents","status":"ok"}
-curl -i https://agents.zopu.puter.wtf/internal/rivet/anything # 404 — registry stays private
```
## Release switch
@@ -103,13 +101,13 @@ From the repository root on the build host, create and promote a release:
scripts/release-agents.sh
```
-The script atomically swaps `/srv/zopu/current`; it deliberately does **not** restart the service. After the upload succeeds, restart the single agents unit:
+The script atomically swaps `/srv/zopu/current`; it deliberately does **not** restart the service. After the upload succeeds, restart the unit:
```sh
sudo systemctl restart zopu-agents.service
```
-Roll back by atomically replacing `current` with a symlink to a prior release, then restarting the same service.
+Roll back by atomically replacing `current` with a symlink to a prior release, then restarting the service.
## Optional: explicit engine ordering
diff --git a/deploy/vds/agents.env.example b/deploy/vds/agents.env.example
index 0bf4bb4..32d2469 100644
--- a/deploy/vds/agents.env.example
+++ b/deploy/vds/agents.env.example
@@ -13,22 +13,29 @@ HOST=127.0.0.1
PORT=3000
NODE_ENV=production
-# --- Rivet Engine + in-process registry (two distinct endpoints) -------------
+# --- Rivet Engine ------------------------------------------------------------
#
# RIVET_ENDPOINT is the Engine CONTROL PLANE this process connects TO for actor
# metadata/management. The engine is a separate private service on this host.
RIVET_ENDPOINT=http://127.0.0.1:6420
#
-# RIVET_SERVERLESS_ENDPOINT is the address the Engine calls BACK INTO this same
-# process to reach the in-process serverless AgentOS registry handler, which is
-# mounted at /internal/rivet/*. It must resolve to this process's own listener
-# plus that base path. Do NOT point this at the engine; do NOT drop the path.
-RIVET_SERVERLESS_ENDPOINT=http://127.0.0.1:3000/internal/rivet
-#
-# Shared secret authenticating actor connections between the engine and this
-# registry's onBeforeConnect. Must match the value configured on the engine.
+# Shared secret authenticating actor connections between the in-process envoy
+# and the AgentOS project workspace actor. Must match the value used by the
+# engine.
RIVET_WORKSPACE_TOKEN=REPLACE-WITH-LONG-RANDOM-WORKSPACE-TOKEN
+# RivetKit runtime selection. These make the native Mode A choice explicit;
+# the registry still connects outbound to the private Engine.
+RIVETKIT_RUNTIME_MODE=envoy
+RIVETKIT_RUNTIME=native
+
+# --- Actor versioning --------------------------------------------------------
+#
+# RivetKit uses this to drain actors when the envoy version changes. Bump it
+# whenever the actor definition or envoy behavior changes in a way that cannot
+# coexist with the previous version.
+RIVET_ENVOY_VERSION=1
+
# --- Flue SQLite persistence -------------------------------------------------
# Canonical conversation state. Parent dir must be one of the unit's
# ReadWritePaths (/srv/zopu/data).
diff --git a/deploy/vds/zopu-agents.service b/deploy/vds/zopu-agents.service
index c639f94..c261526 100644
--- a/deploy/vds/zopu-agents.service
+++ b/deploy/vds/zopu-agents.service
@@ -1,33 +1,29 @@
-# zopu-agents.service — single Node intelligence runtime for the Zopu VDS.
+# zopu-agents.service — single Zopu intelligence runtime for the VDS.
#
-# Runtime shape (one process, no separate runner):
+# One process hosts both the Hono/Flue server and the AgentOS registry running
+# in native envoy mode (Rivet Mode A):
#
# zopu-agents (this unit)
# └─ node /srv/zopu/current/packages/agents/dist/server.mjs
# ├─ Hono HTTP server on 127.0.0.1:3000
# ├─ Flue 2.0 conversation agents + SQLite at /srv/zopu/data/flue
-# └─ AgentOS registry in serverless mode, served in-process at
-# /internal/rivet/* (registry.handler drives the per-request
-# runtime; there is no standalone AgentOS runner process).
+# └─ AgentOS registry in envoy/native mode (registry.startAndWait)
+#
+# The AgentOS actor is hosted in-process: the Hono/Flue process is also the
+# envoy that connects outbound to the Rivet Engine. There is no separate
+# runner process and no Rivet HTTP handler route.
#
# The Rivet Engine is a SEPARATE private service on 127.0.0.1:6420, managed
# by its own unit/compose. This unit does not start or own it; it only waits
# for the engine health endpoint before binding (ExecStartPre below).
-#
-# Two distinct Rivet endpoints (see agents.env):
-# RIVET_ENDPOINT — engine control plane the registry connects to
-# (http://127.0.0.1:6420). Used for actor metadata.
-# RIVET_SERVERLESS_ENDPOINT — address the engine calls BACK into this same
-# process's /internal/rivet handler
-# (http://127.0.0.1:3000/internal/rivet).
-# These are NOT interchangeable; do not point both at the engine.
+# RIVET_ENDPOINT is the engine control plane (http://127.0.0.1:6420).
#
# The release is a self-contained bundle under /srv/zopu/releases/ with
# /srv/zopu/current as the live symlink. This unit always runs `current`; the
# release script swaps it atomically, then the operator restarts this service.
[Unit]
-Description=Zopu agents intelligence runtime (Flue + in-process AgentOS registry)
+Description=Zopu agents intelligence runtime (Hono + Flue + in-process envoy)
# Soft network ordering. The real gate on the separate Engine is the health
# probe in ExecStartPre. If the engine is itself a named systemd unit on this
# host, add an explicit ordering here, e.g.:
diff --git a/docs/DEPLOYMENT_PLAN.md b/docs/DEPLOYMENT_PLAN.md
index eef4c5d..5540fc1 100644
--- a/docs/DEPLOYMENT_PLAN.md
+++ b/docs/DEPLOYMENT_PLAN.md
@@ -12,7 +12,7 @@ Use a deliberately split stack:
Fast iteration (private development) Stable staging (public)
──────────────────────────────────── ────────────────────────
Mac + Tailscale Vercel + Contabo VDS
-Web / Flue + in-process registry / Rivet Web SSR Agents VDS
+Web / Flue + AgentOS registry (one process) Web SSR Agents VDS
│ │ │
personal Convex dev deployment staging Convex deployment
```
@@ -22,8 +22,7 @@ personal Convex dev deployment staging Convex deployment
| User-visible URL | Existing Tailscale URL on the Mac | `https://zopu-staging.vercel.app` on Vercel |
| Product backend | Personal Convex **dev** deployment | Dedicated long-lived Convex deployment |
| Web | Vite HMR on the Mac | Vercel React Router SSR |
-| Agent worker | Local Flue + in-process AgentOS registry and Engine | Contabo VDS: one Flue/registry Node process + Rivet Engine |
-| Delivery | Run local `pnpm dev:tailscale` | Locally built release bundle promoted on the VDS |
+| Agent worker | Local Flue + AgentOS registry (one Node process) + Engine | Contabo VDS: one Flue + AgentOS registry Node process + Rivet Engine |
| State/credentials | Development-only | Independent staging secrets, OAuth app, and volumes |
**Do not create a second remote “fast iteration” stack.** It duplicates the slowest part of the loop—building and replacing container images—while the existing Mac/Tailscale stack already exercises the complete topology from a phone. Staging is the public link and integration gate; local development is the fast environment.
@@ -69,9 +68,9 @@ flowchart LR
Browser[Browser] -->|HTTPS| Vercel[Vercel: zopu-staging.vercel.app]
Browser -->|queries & mutations| Convex[Convex: joyous-cat-297]
Vercel -->|/api/auth rewrite| ConvexSite[Convex Site: joyous-cat-297.convex.site]
- Convex -->|FLUE_DB_TOKEN + org header| Flue[Contabo: Flue + AgentOS registry]
- Flue -->|control plane| Engine[Rivet Engine: 127.0.0.1:6420]
- Engine -->|serverless callback| Flue
+ Convex -->|FLUE_DB_TOKEN + org header| Flue[Contabo: Flue + AgentOS registry one process]
+ Flue -->|control plane + outbound envoy| Engine[Rivet Engine: 127.0.0.1:6420]
+ Engine --> Flue
Engine --> Volumes[/srv/zopu/data/rivet]
Flue --> Workspaces[/srv/zopu/workspaces + /srv/zopu/data/flue]
CF[Cloudflare DNS] --> Vercel
@@ -93,7 +92,7 @@ The current VDS (`zopu-staging-1`, `158.220.110.74`) runs a deliberately small p
| Service | Runtime | Network exposure | Persistent data | Notes |
| --- | --- | --- | --- | --- |
| `engine` | Existing Docker Compose (`/srv/zopu/compose/docker-compose.yml`) | private loopback `127.0.0.1:6420` | `/srv/zopu/data/rivet` bind mount | Single-node RocksDB backend. Health: `http://127.0.0.1:6420/health`. |
-| `agents` | systemd (`/etc/systemd/system/zopu-agents.service`) | Caddy upstream only (`127.0.0.1:3000`) | `/srv/zopu/workspaces` + `/srv/zopu/data/flue` | One Node process runs Flue and its serverless AgentOS registry. `RIVET_ENDPOINT` is the Engine control plane; `RIVET_SERVERLESS_ENDPOINT` is Engine's private callback to `http://127.0.0.1:3000/internal/rivet`. |
+| `agents` | systemd (`/etc/systemd/system/zopu-agents.service`) | Caddy upstream only (`127.0.0.1:3000`) | `/srv/zopu/workspaces` + `/srv/zopu/data/flue` | One Node process runs Flue **and** hosts the AgentOS registry in native envoy Mode A (`registry.startAndWait()`). `RIVET_ENDPOINT` is the Engine control plane; the registry opens an outbound connection to the Engine, which routes actor calls back over it. No second runner process and no inbound serverless callback route. |
| `caddy` | systemd (`/etc/systemd/system/caddy.service`) | 80/443 only | Caddy certificate/config volumes | `/etc/caddy/Caddyfile` terminates TLS for `agents.zopu.puter.wtf` and forwards only the documented Flue paths to `127.0.0.1:3000`. |
Release artifacts are source controlled: `scripts/release-agents.sh` builds a curated bundle locally and atomically promotes `/srv/zopu/current`; `deploy/vds/` contains the systemd unit and secret-free environment template. The VDS materializes Linux production dependencies from the frozen lockfile, so macOS-native binaries are never uploaded.
@@ -107,7 +106,7 @@ Dokploy is intentionally excluded: it has already proved too slow and imperative
| Layer | Source of truth | Tool | Reason |
| --- | --- | --- | --- |
| VDS baseline | `infra/ansible` | **Ansible** | Idempotent OS convergence: service user, Docker, firewall, Tailscale, directories, Caddy prerequisites, and backup timer. No Pulumi SSH-command pseudo-provider. |
-| VDS application topology | `deploy/vds` + `/srv/zopu/compose` | **systemd + Docker Compose** | Engine stays in its existing private Compose service; the checked-in systemd unit runs the single Node agents/registry process. |
+| | VDS application topology | `deploy/vds` + `/srv/zopu/compose` | **systemd + Docker Compose** | Engine stays in its existing private Compose service; one checked-in systemd unit runs the single Flue + AgentOS registry Node process (native envoy Mode A). |
| Release execution | `scripts/release-agents.sh` now; CI later | **Local release bundle promotion** | Builds a curated cross-platform bundle, installs Linux dependencies on the VDS, atomically swaps `current`, then restarts `zopu-agents`. |
> **Current state (2026-08-03):** the Engine and Caddy retain their manually bootstrapped VDS services. The Node agents process is now represented by source-controlled `deploy/vds` artifacts and `scripts/release-agents.sh`; Pulumi/Ansible and CI are still future work.
@@ -129,12 +128,10 @@ Ansible is not redundant: it makes the Contabo host reproducible without pretend
1. Run repository validation and build the staging web artifact for the staging Convex deployment (`joyous-cat-297`, `https://joyous-cat-297.convex.cloud`), then deploy it to `zopu-staging` (`https://zopu-staging.vercel.app`).
2. From the repository root, create and promote the agents release with `scripts/release-agents.sh `; it installs Linux production dependencies on the VDS, atomically swaps `/srv/zopu/current`, and leaves the running process untouched until restart.
-3. Restart `zopu-agents.service` on the VDS. The unit waits for Engine health, then starts the one Node process that exposes Flue and the serverless registry.
-4. Verify: `https://zopu-staging.vercel.app` returns 200, Convex auth origin `https://zopu-staging.vercel.app` is accepted, `https://agents.zopu.puter.wtf/health` returns 200, Engine health returns 200 at `http://127.0.0.1:6420/health` inside the private network, and a real signed-in staging conversation receives an agent response.
+3. Restart `zopu-agents.service` on the VDS. The unit waits for Engine health, then starts the single Node process that serves Flue and starts the AgentOS registry in native envoy Mode A.
+4. Verify: `https://zopu-staging.vercel.app` returns 200, Convex auth origin `https://zopu-staging.vercel.app` is accepted, `https://agents.zopu.puter.wtf/health` returns 200, Engine health returns 200 at `http://127.0.0.1:6420/health` inside the private network, `zopu-agents.service` is active, the registry envoy connected to the engine (check the process log for the startup line), and a real signed-in staging conversation receives an agent response.
5. Roll back by atomically repointing `/srv/zopu/current` at the prior release then restarting `zopu-agents.service`.
-Do not couple the Vercel deployment to Gitea-native Git integration assumptions. The repository is hosted on Gitea, so start with CI invoking the Vercel CLI/API. If a Git mirror is later introduced, Vercel previews can be enabled separately.
-
## Required implementation backlog
The staging environment is live. Remaining work is to close the gap between the manual, on-host configuration and the declarative, CI-driven target described above:
@@ -142,7 +139,7 @@ The staging environment is live. Remaining work is to close the gap between the
1. **Convex staging deployment** — ✅ Done as `joyous-cat-297`. `SITE_URL` is set to `https://zopu-staging.vercel.app`, `FLUE_URL`/`AGENT_BACKEND_URL` to `https://agents.zopu.puter.wtf`, and `FLUE_DB_TOKEN` to the VDS agents token.
2. **Auth/webhook origin seams** — ✅ Vercel rewrites `/api/auth/*` to `https://joyous-cat-297.convex.site/api/auth/*` in `vercel.json`. Verify that Puter/Gitea webhooks are configured to post directly to the Convex Site HTTP action URL (`https://joyous-cat-297.convex.site/api/git/webhooks/...`) rather than the web origin.
3. **Vercel React Router staging project** — ✅ Done as `zopu-staging` (`https://zopu-staging.vercel.app`).
-4. **VDS agent topology** — ✅ Source-controlled VDS unit, env template, and atomic release bundle now model the single Flue + in-process registry process. Apply them to the VDS and retire the former runner service from its Compose file.
+4. **VDS agent topology** — ✅ Source-controlled VDS unit, env template, and atomic release bundle now model the single Flue + AgentOS registry Node process running in native envoy Mode A. Apply them to the VDS and retire the former separate runner service.
5. **Engine deployment, backups, and CI** — 🔄 Engine health and VDS backups exist. Move the existing Engine Compose service and Caddy configuration into source-controlled provisioning; add CI to run validation, promote release bundles, and deploy the web.
6. **Declarative IaC** — ⏳ Create `infra/pulumi` (Cloudflare DNS + Vercel project) and `infra/ansible` (host convergence).
7. **End-to-end staging smoke** — ⏳ Execute: sign in via GitHub OAuth, create/connect a project, send a conversation, and complete a disposable issue-to-PR job against the live `zopu-staging` + `agents.zopu.puter.wtf` stack.
@@ -153,7 +150,7 @@ The staging environment is live. Remaining work is to close the gap between the
- Use unique `FLUE_DB_TOKEN`, Rivet admin token, workspace token, model credential, and Git token per environment.
- The VDS runs code-writing agents. Use a dedicated service user; do not mount the host home directory; expose only scoped repository credentials to individual attempts; and keep staging separate from any production host.
- Back up Rivet engine state and Caddy configuration. Treat workspaces as reproducible/ephemeral unless an active job requires retention; prune completed worktrees deliberately.
-- A single Flue Node instance is the correct initial staging shape. Its durable Convex adapter survives restarts, but each conversation still requires one live owner—do not add replicas until ownership routing is designed. [Flue database guide](https://flueframework.com/docs/guide/database)
+- A single Flue + AgentOS registry Node instance (native envoy Mode A) is the correct initial staging shape. Its durable Convex adapter survives restarts, but each conversation still requires one live owner—do not add replicas until ownership routing is designed. [Flue database guide](https://flueframework.com/docs/guide/database)
## Sources
diff --git a/docs/LOCAL_SETUP.md b/docs/LOCAL_SETUP.md
index 49ae531..493c7d4 100644
--- a/docs/LOCAL_SETUP.md
+++ b/docs/LOCAL_SETUP.md
@@ -7,7 +7,7 @@ This guide runs the active Zopu stack locally, including the browser chat and th
```text
Browser (React Router/Vite, :5173)
-> Convex Cloud (durable product state, auth, workflows)
- -> Flue agent server + in-process AgentOS registry (:3585)
+ -> Flue agent server + AgentOS registry (:3585, one Node process)
-> Rivet Engine control plane (:6420)
-> AgentOS workspace actor (Git + repo clone mounted from the local checkout)
-> Gitea branch and pull request
@@ -82,15 +82,15 @@ AGENT_MODEL_MAX_TOKENS=
### Rivet and AgentOS
```env
-# The private Engine control plane this process connects to.
+# The private Engine control plane the registry connects to.
RIVET_ENDPOINT=http://127.0.0.1:6420
-# The path where the Engine calls the registry co-hosted in Flue.
-RIVET_SERVERLESS_ENDPOINT=http://127.0.0.1:3585/internal/rivet
RIVET_WORKSPACE_TOKEN=
AGENT_WORKSPACE_ROOT=/absolute/path/to/zopu-agent-workspaces
+# Optional: bump when the actor definition changes to drain old actors.
+RIVET_ENVOY_VERSION=0.0.0
```
-The registry runs in the Flue process; there is no standalone AgentOS runner. `RIVET_ENDPOINT` and `RIVET_SERVERLESS_ENDPOINT` have opposite directions and are not interchangeable. Port `6421` is an internal Engine API-peer port and must not be used as `RIVET_ENDPOINT`.
+The Hono/Flue Node process hosts the AgentOS registry in **native envoy Mode A** (`registry.startAndWait()`). The registry opens one persistent outbound connection to the Rivet Engine; the Engine routes actor calls back over that connection, so no inbound HTTP callback is required. Port `6421` is an internal Engine API-peer port and must not be used as `RIVET_ENDPOINT`. Do not set `RIVET_SERVERLESS_URL` or `RIVET_SERVERLESS_TOKEN`; they are unused in this topology.
### Gitea
@@ -136,7 +136,7 @@ If work execution uses a separate agent URL, set `AGENT_BACKEND_URL`; otherwise
## Start the stack
-Run each process in its own terminal. The Engine must be healthy before Flue starts because Flue serves the in-process registry that uses it.
+Run each process in its own terminal. The Engine must be healthy before the agents process starts.
### 1. Start Convex development
@@ -154,7 +154,7 @@ Both commands run from `packages/backend` and explicitly load the repository-roo
Start exactly one Engine listening on `127.0.0.1:6420` using the local Engine deployment mechanism. Do not use port `6421` as the application endpoint.
-### 3. Start Flue and the in-process registry
+### 3. Start the agents process
```bash
# Laptop-only access
@@ -163,11 +163,11 @@ pnpm --filter @code/agents dev -- --port 3585
# Convex callbacks or access from another device
HOST=0.0.0.0 \
RIVET_ENDPOINT=http://127.0.0.1:6420 \
-RIVET_SERVERLESS_ENDPOINT=http://127.0.0.1:3585/internal/rivet \
+FLUE_URL=http://:3585 \
pnpm --filter @code/agents dev -- --port 3585
```
-The Flue development server serves both agent routes and the AgentOS registry at `/internal/rivet/*`; never start a separate registry runner. If shell exports override `.env`, set both endpoints explicitly as shown.
+This single process serves the Hono/Flue routes **and** starts the AgentOS registry in native envoy Mode A during app module initialization (`registry.startAndWait()`). The registry opens an outbound connection to the Rivet Engine; actor calls are routed back over that connection, so there is no separate runner process and no inbound serverless callback route. If shell exports override `.env`, set `RIVET_ENDPOINT` explicitly.
### 4. Start the web application
@@ -222,12 +222,10 @@ Web sends a conversation mutation to Convex
-> the web observes Convex's reactive projection
For code execution:
- -> Flue calls the local AgentOS harness
- -> the harness creates an isolated Git worktree
- -> a Rivet actor boots an AgentOS VM and mounts the worktree
- -> Pi implements the issue and produces a candidate revision
- -> the host pushes a unique branch
- -> Tea creates a Gitea pull request
+ -> Flue calls the AgentOS actor through the Rivet Engine
+ -> a Rivet actor boots an AgentOS VM with the project workspace
+ -> the actor clones the repository and runs validated commands
+ -> the actor writes artifacts back through the actor RPC
```
## Common failures
@@ -245,20 +243,20 @@ Set Convex `SITE_URL` to the exact browser origin. The shared development deploy
### AgentOS fails while mounting the repository
- Confirm the Engine is healthy at `http://127.0.0.1:6420/health`.
-- Confirm Flue is running with `RIVET_ENDPOINT=http://127.0.0.1:6420`.
-- Confirm `RIVET_SERVERLESS_ENDPOINT` points back to the active Flue listener with `/internal/rivet` appended.
+- Confirm the agents process is running and the registry started the envoy connection on startup.
+- Confirm the process has `RIVET_ENDPOINT=http://127.0.0.1:6420`.
- Confirm `AGENT_WORKSPACE_ROOT` is writable.
-- Restart Flue after changing registry configuration; its development server owns the in-process registry.
+- Restart the agents process after changing the actor configuration; the registry re-registers actor type changes with the engine on startup.
-The harness clients require CBOR encoding for actor RPC. Do not remove the `encoding: "cbor"` configuration in `packages/agents/src/adapters/agentos.ts`.
+The actor client requires CBOR encoding for actor RPC. Do not remove the `encoding: "cbor"` configuration in `packages/agents/src/adapters/agentos.ts`.
### AgentOS reports an ACP completed-message resource limit
-Restart Flue so the in-process registry picks up its updated actor configuration.
+Restart the agents process so the registry re-registers its updated actor configuration with the engine.
-### Flue connects to a remote Rivet deployment unexpectedly
+### The process connects to a remote Rivet deployment unexpectedly
-Environment variables exported by the shell override values loaded from `.env`. Start Flue with explicit `RIVET_ENDPOINT=http://127.0.0.1:6420` and `RIVET_SERVERLESS_ENDPOINT=http://127.0.0.1:3585/internal/rivet`.
+Environment variables exported by the shell override values loaded from `.env`. Start the agents process with explicit `RIVET_ENDPOINT=http://127.0.0.1:6420`.
### Gitea issue or PR commands fail
diff --git a/package.json b/package.json
index a51b50d..137f17c 100644
--- a/package.json
+++ b/package.json
@@ -98,8 +98,12 @@
"vitest": "catalog:"
},
"overrides": {
+ "@rivetkit/engine-envoy-protocol": "2.3.10",
+ "@rivetkit/framework-base": "2.3.10",
+ "@rivetkit/react": "2.3.10",
"react": "19.2.8",
"react-dom": "19.2.8",
+ "rivetkit": "2.3.10",
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.2"
},
"packageManager": "pnpm@11.17.0"
diff --git a/packages/agents/src/actors/project-workspace.ts b/packages/agents/src/actors/project-workspace.ts
new file mode 100644
index 0000000..10c6478
--- /dev/null
+++ b/packages/agents/src/actors/project-workspace.ts
@@ -0,0 +1,30 @@
+import git from "@agentos-software/git";
+import { agentOS } from "@rivet-dev/agentos";
+
+/**
+ * Factory for the durable AgentOS project workspace actor.
+ *
+ * One VM is created per project (keyed structurally by [projectId]). The actor
+ * bundles Git alongside the AgentOS default utilities so the runtime can clone
+ * and inspect repositories inside an isolated sandbox.
+ *
+ * The actor is defined in a shared module so the same shape is used by:
+ * - `src/runner.ts` — typed registry export used by `src/app.ts` to start the
+ * in-process envoy (native Mode A) and by the AgentOS adapter for
+ * client-side typing.
+ *
+ * `onBeforeConnect` validates the shared workspace token so only this
+ * deployment's runtime can drive the actor.
+ */
+export const createProjectWorkspaceActor = (token: string) =>
+ agentOS({
+ defaultSoftware: true,
+ onBeforeConnect: (_context, params) => {
+ if (params.token !== token) {
+ throw new Error("Unauthorized workspace connection");
+ }
+ },
+ // Git is required for clone, rev-parse, and read-only repository inspection.
+ // defaultSoftware provides sh/coreutils/common utilities only.
+ software: [git],
+ });
diff --git a/packages/agents/src/adapters/agentos.ts b/packages/agents/src/adapters/agentos.ts
index 80eb03e..5d4a553 100644
--- a/packages/agents/src/adapters/agentos.ts
+++ b/packages/agents/src/adapters/agentos.ts
@@ -10,12 +10,15 @@ import type { registry } from "../runner";
// by projectId, and exposes only safe read-oriented operations for the
// onboarding runtime endpoint and the conversation agent's tools.
//
-// The adapter talks to the durable AgentOS RivetKit actor registered in this
-// same process (src/runner.ts, served in serverless mode via the Hono app).
-// It caches only the lightweight actor client, never VM handles — so it
-// survives engine restarts and process recycling. Each operation re-resolves
-// the actor handle via getOrCreate + resolve, which is stateless and safe
-// across lifecycle boundaries.
+// The durable AgentOS RivetKit actor is hosted in-process by the Flue/Hono
+// Node process (native envoy Mode A, started from src/app.ts). This adapter
+// uses the typed registry export from src/runner.ts only for client-side
+// typing; it connects to the actor through the Rivet Engine, not an
+// in-process registry handler. It caches only
+// the lightweight actor client, never VM handles — so it survives engine
+// restarts and process recycling. Each operation re-resolves the actor handle
+// via getOrCreate + resolve, which is stateless and safe across lifecycle
+// boundaries.
//
// It does NOT use Pi for onboarding — Pi capability is preserved only for
// later GLM-5.2 work, and this module never invokes it.
diff --git a/packages/agents/src/app.ts b/packages/agents/src/app.ts
index 3b6bbaa..de8f2b5 100644
--- a/packages/agents/src/app.ts
+++ b/packages/agents/src/app.ts
@@ -22,6 +22,41 @@ const app = new Hono();
// --- Health (unauthenticated liveness) --------------------------------------
app.get("/health", (c) => c.json({ service: "zopu-agents", status: "ok" }));
+// --- AgentOS envoy lifecycle (native Mode A) --------------------------------
+//
+// The durable project-workspace actor is hosted in-process. startAndWait()
+// opens the outbound envoy connection to the Rivet Engine and resolves only
+// once the envoy has registered. A rejection here propagates through the Flue
+// bootstrap (loadFlueNodeApplication) and crashes the process at startup
+// before the HTTP listener accepts traffic — the desired fail-fast behavior.
+//
+// shutdown.disableSignalHandlers is set on the registry (see runner.ts), so
+// this module owns registry teardown. The Flue-generated entry retains
+// process-exit ownership; this handler only drains the registry in parallel
+// with Flue's own graceful-shutdown drain and never calls process.exit().
+await registry.startAndWait();
+
+let shuttingDown = false;
+const shutdownRegistry = async (): Promise => {
+ if (shuttingDown) {
+ return;
+ }
+ shuttingDown = true;
+ try {
+ await registry.shutdown();
+ } catch (error) {
+ console.error(
+ "[agents] registry.shutdown() failed during shutdown:",
+ error
+ );
+ }
+};
+process.on("SIGINT", () => {
+ void shutdownRegistry();
+});
+process.on("SIGTERM", () => {
+ void shutdownRegistry();
+});
// --- Auth -------------------------------------------------------------------
@@ -243,11 +278,4 @@ protectedAgent.use("*", async (c, next) => {
protectedAgent.route("/", agentSubApp as unknown as Hono);
app.route("/agents/project-conversation", protectedAgent);
-// --- In-process RivetKit registry (serverless handler) -----------------------
-//
-// The AgentOS registry runs in serverless mode inside this same process.
-// Mounted on a private path so only internal callers (and the Engine via
-// configurePool.url) reach it. registry.handler drives the per-request
-// serverless runtime; importing the registry object performs no start.
-app.all("/internal/rivet/*", (c) => registry.handler(c.req.raw));
export default app;
diff --git a/packages/agents/src/runner.ts b/packages/agents/src/runner.ts
index de16bb3..56fa34e 100644
--- a/packages/agents/src/runner.ts
+++ b/packages/agents/src/runner.ts
@@ -1,64 +1,33 @@
-import git from "@agentos-software/git";
import { env } from "@code/env/agent";
-import { agentOS, setup } from "@rivet-dev/agentos";
+import { setup } from "@rivet-dev/agentos";
+
+import { createProjectWorkspaceActor } from "./actors/project-workspace.ts";
/**
- * AgentOS registry — owns the durable project workspace actor.
+ * AgentOS registry hosted in-process by the Flue/Hono Node process in native
+ * envoy Mode A.
*
- * The actor is keyed structurally by [projectId] (project ids are globally
- * unique), so each project resolves to one dedicated VM across the process's
- * lifetime. The registry runs in serverless runtime mode and is served
- * in-process by the Hono app (see app.ts), which mounts
- * `registry.handler(c.req.raw)` at `/internal/rivet/*`. There is no
- * standalone runner process and no `startAndWait` entry point.
+ * The registry opens one persistent outbound connection to the Rivet Engine
+ * (`RIVET_ENDPOINT`). The Engine routes actor calls back over that connection,
+ * so no inbound HTTP callback (`/api/rivet/*`, `configurePool`) is required.
+ * This is the single-process topology: the Hono app and the durable actor
+ * runtime share one process, and `registry.startAndWait()` is driven from
+ * `app.ts` during module initialization.
*
- * `configurePool.url` is the distinct endpoint the Engine invokes to reach
- * this registry handler; it is NOT the Engine control-plane URL (that stays
- * `endpoint: env.RIVET_ENDPOINT`). The actor still authenticates every
- * connection via `onBeforeConnect` against RIVET_WORKSPACE_TOKEN, so only
- * this deployment's adapter can drive the actor.
- *
- * Software: the actor bundles Git (@agentos-software/git) alongside the
- * AgentOS default utilities. defaultSoftware alone is common utilities, not
- * Git — without the explicit git entry, clone/rev-parse/grep would fail.
- *
- * Import-safe: this module exports a pure registry object and performs no
- * top-level process start. Importing it (including `import type`) does not
- * start a registry.
+ * `shutdown.disableSignalHandlers` is set so the Flue-generated server entry
+ * owns process signals (SIGINT/SIGTERM/disconnect). `app.ts` installs a
+ * host-owned handler that awaits `registry.shutdown()` alongside Flue's own
+ * drain; neither path calls `process.exit()` from here.
*/
+export const projectWorkspace = createProjectWorkspaceActor(
+ env.RIVET_WORKSPACE_TOKEN
+);
-// Project workspace actor — one VM per project, with Git + default utilities.
-const projectWorkspace = agentOS({
- defaultSoftware: true,
- onBeforeConnect: (_context, params) => {
- if (params.token !== env.RIVET_WORKSPACE_TOKEN) {
- throw new Error("Unauthorized workspace connection");
- }
- },
- // Git is required for clone, rev-parse, and read-only repository inspection.
- // defaultSoftware provides sh/coreutils/common utilities only.
- software: [git],
-});
-
-// Internal mount path shared by the Hono app route and the registry's
-// serverless base path. Both MUST agree — the handler uses this base path to
-// route actor/metadata requests, and the app must mount it at the same path.
-export const RIVET_SERVERLESS_BASE_PATH = "/internal/rivet";
-
-// Registry: the single actor type served by this process, in serverless mode.
-// `serverless.basePath` matches the app route mount point; `configurePool.url`
-// is the address the Engine calls back into this handler. Importing this
-// module does not start the registry — the handler is driven per-request by
-// the Hono app.
export const registry = setup({
- configurePool: {
- url: env.RIVET_SERVERLESS_ENDPOINT,
- },
endpoint: env.RIVET_ENDPOINT,
logging: { level: "info" },
- serverless: {
- basePath: RIVET_SERVERLESS_BASE_PATH,
- },
+ runtime: "native",
+ shutdown: { disableSignalHandlers: true },
use: {
projectWorkspace,
},
diff --git a/packages/backend/convex/projectRuntimes.ts b/packages/backend/convex/projectRuntimes.ts
index 3fe7a5b..472b4ab 100644
--- a/packages/backend/convex/projectRuntimes.ts
+++ b/packages/backend/convex/projectRuntimes.ts
@@ -43,6 +43,7 @@ export const requestRuntime = internalMutation({
}
const timestamp = Date.now();
return await ctx.db.insert("projectRuntimes", {
+ attempt: 1,
createdAt: timestamp,
idempotencyKey: args.idempotencyKey,
organizationId: args.organizationId,
@@ -55,13 +56,16 @@ export const requestRuntime = internalMutation({
});
/**
- * Advance a ProjectRuntime to a new phase. A terminal "ready" or "failed"
- * status cannot be overwritten by an earlier phase on retry, so a completed
- * setup is never partially rewound. Idempotent: re-applying the same status
- * is a no-op.
+ * Advance a ProjectRuntime to a new phase. A terminal "ready" status is never
+ * overwritten, so a completed setup is never partially rewound. Phase writes
+ * are scoped to a setup attempt: a write whose `attempt` predates the row's
+ * current attempt is treated as a no-op (it belongs to a superseded setup
+ * attempt), which lets a retry run with fresh, non-colliding lifecycle events.
+ * Idempotent: re-applying the same status within the same attempt is a no-op.
*/
export const markStatus = internalMutation({
args: {
+ attempt: v.optional(v.number()),
lastError: v.optional(v.string()),
repositoryCommit: v.optional(v.string()),
repositoryPath: v.optional(v.string()),
@@ -76,12 +80,19 @@ export const markStatus = internalMutation({
if (!runtime) {
throw new ConvexError("Project runtime not found");
}
- // Never rewind a terminal state. A retry that re-enters the coordinator
- // after success must observe readiness, not re-run phases.
- if (
- (runtime.status === "ready" || runtime.status === "failed") &&
- args.status !== runtime.status
- ) {
+ const currentAttempt = runtime.attempt ?? 1;
+ // Never rewind a terminal "ready" state. A retry that re-enters the
+ // coordinator after success must observe readiness, not re-run phases.
+ if (runtime.status === "ready" && args.status !== "ready") {
+ return runtime;
+ }
+ // Stale-attempt guard: once a runtime has advanced to a newer attempt
+ // (via retry), phase writes from a prior attempt are ignored. This
+ // preserves prior history without letting an old attempt suppress a
+ // current-attempt transition. The failed-write that ended the prior
+ // attempt has the same attempt value, so this never blocks the in-flight
+ // attempt from recording its own failure.
+ if (args.attempt !== undefined && args.attempt < currentAttempt) {
return runtime;
}
const patch: Record = {
@@ -115,6 +126,43 @@ export const markStatus = internalMutation({
},
});
+/**
+ * Reset a failed ProjectRuntime for a fresh setup attempt: increment the
+ * durable attempt counter, flip status back to "requested", and clear the
+ * prior failure metadata. Only a "failed" runtime may be retried; any other
+ * status (including "ready") is returned unchanged with `retried: false` so a
+ * retry of an already-ready runtime never re-runs setup. History (prior
+ * lifecycle/ready/failed events) is preserved — only the runtime row moves
+ * forward, and the new attempt's coordinator uses distinct idempotency keys.
+ */
+export const retry = internalMutation({
+ args: { runtimeRowId: v.id("projectRuntimes") },
+ handler: async (
+ ctx,
+ args
+ ): Promise<{ retried: boolean; runtime: Doc<"projectRuntimes"> }> => {
+ const runtime = await ctx.db.get(args.runtimeRowId);
+ if (!runtime) {
+ throw new ConvexError("Project runtime not found");
+ }
+ if (runtime.status !== "failed") {
+ return { retried: false, runtime };
+ }
+ const nextAttempt = (runtime.attempt ?? 1) + 1;
+ await ctx.db.patch(args.runtimeRowId, {
+ attempt: nextAttempt,
+ lastError: undefined,
+ status: "requested",
+ updatedAt: Date.now(),
+ });
+ const updated = await ctx.db.get(args.runtimeRowId);
+ if (!updated) {
+ throw new ConvexError("Project runtime could not be read after update");
+ }
+ return { retried: true, runtime: updated };
+ },
+});
+
export const getForProject = internalQuery({
args: { projectId: v.id("projects") },
handler: async (ctx, args): Promise | null> =>
diff --git a/packages/backend/convex/projectSetup.ts b/packages/backend/convex/projectSetup.ts
index bab6f9a..99d8231 100644
--- a/packages/backend/convex/projectSetup.ts
+++ b/packages/backend/convex/projectSetup.ts
@@ -8,18 +8,30 @@ import type { Id } from "./_generated/dataModel";
import { internalAction } from "./_generated/server";
import type { ActionCtx } from "./_generated/server";
-// Idempotency keys (shared contract):
+// Idempotency keys (shared contract). Setup attempt `1` preserves the original
+// keys verbatim (`...:v1`) so the existing import path is unchanged; a later
+// attempt appends `:a` so its lifecycle/ready/failed events can never
+// collide with or be suppressed by a prior attempt's events.
+/** Empty for the first attempt (preserving the original key space); `:a` for
+ * any retry so its events are distinct from every prior attempt's. */
+const attemptSuffix = (attempt: number): string =>
+ attempt <= 1 ? "" : `:a${attempt}`;
+
const runtimeIdempotencyKey = (projectId: Id<"projects">) =>
`project:${projectId}:vm:v1`;
-const readyEventIdempotencyKey = (projectId: Id<"projects">) =>
- `project:${projectId}:ready:v1`;
+const readyEventIdempotencyKey = (projectId: Id<"projects">, attempt: number) =>
+ `project:${projectId}:ready:v1${attemptSuffix(attempt)}`;
const dispatchIdempotencyKey = (eventId: Id<"events">, agentId: string) =>
`event:${eventId}:agent:${agentId}:handler:v1`;
const lifecycleEventIdempotencyKey = (
projectId: Id<"projects">,
- phase: string
-) => `project:${projectId}:setup:${phase}:v1`;
-
+ phase: string,
+ attempt: number
+) => `project:${projectId}:setup:${phase}:v1${attemptSuffix(attempt)}`;
+const failedEventIdempotencyKey = (
+ projectId: Id<"projects">,
+ attempt: number
+) => `${readyEventIdempotencyKey(projectId, attempt)}:failed`;
const PROJECT_AGENT_TYPE = "project";
const requestRuntimeRef = makeFunctionReference<
@@ -35,6 +47,7 @@ const requestRuntimeRef = makeFunctionReference<
const markRuntimeStatusRef = makeFunctionReference<
"mutation",
{
+ attempt?: number;
runtimeRowId: Id<"projectRuntimes">;
status: string;
runtimeId?: string;
@@ -164,6 +177,7 @@ interface SetupRuntimeResult {
interface RuntimeRow {
_id: Id<"projectRuntimes">;
+ attempt: number;
status: string;
organizationId: Id<"organizations">;
}
@@ -175,6 +189,7 @@ const asRuntimeRow = (doc: unknown): RuntimeRow => {
}
return {
_id: row._id,
+ attempt: row.attempt ?? 1,
organizationId: row.organizationId,
status: row.status ?? "requested",
};
@@ -313,6 +328,8 @@ export const runSetup = internalAction({
throw new ConvexError("Project not found within organization");
}
+ const branch = source.defaultBranch ?? "main";
+
// Request (or reuse) the runtime row. Terminal-safe: a ready/failed row is
// returned as-is by the mutation.
const runtimeRowId = await ctx.runMutation(requestRuntimeRef, {
@@ -347,10 +364,11 @@ export const runSetup = internalAction({
};
}
- const branch = source.defaultBranch ?? "main";
+ const { attempt } = current;
// --- Phase: creating_vm ---
await ctx.runMutation(markRuntimeStatusRef, {
+ attempt,
runtimeRowId,
status: "creating_vm",
});
@@ -359,7 +377,8 @@ export const runSetup = internalAction({
correlationId: args.correlationId,
idempotencyKey: lifecycleEventIdempotencyKey(
args.projectId,
- "creating_vm"
+ "creating_vm",
+ attempt
),
organizationId: args.organizationId,
payload: {},
@@ -369,15 +388,19 @@ export const runSetup = internalAction({
visibility: "internal",
});
- // --- Phase: cloning + checking_repository + env scan (single Hono call) ---
await ctx.runMutation(markRuntimeStatusRef, {
+ attempt,
runtimeRowId,
status: "cloning",
});
await ctx.runMutation(appendSystemEventRef, {
actorService: "project-setup",
correlationId: args.correlationId,
- idempotencyKey: lifecycleEventIdempotencyKey(args.projectId, "cloning"),
+ idempotencyKey: lifecycleEventIdempotencyKey(
+ args.projectId,
+ "cloning",
+ attempt
+ ),
organizationId: args.organizationId,
payload: { branch },
projectId: args.projectId,
@@ -396,6 +419,7 @@ export const runSetup = internalAction({
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
await ctx.runMutation(markRuntimeStatusRef, {
+ attempt,
lastError: message,
runtimeRowId,
status: "failed",
@@ -403,7 +427,7 @@ export const runSetup = internalAction({
await ctx.runMutation(appendSystemEventRef, {
actorService: "project-setup",
correlationId: args.correlationId,
- idempotencyKey: `${readyEventIdempotencyKey(args.projectId)}:failed`,
+ idempotencyKey: failedEventIdempotencyKey(args.projectId, attempt),
organizationId: args.organizationId,
payload: { error: message },
projectId: args.projectId,
@@ -421,6 +445,7 @@ export const runSetup = internalAction({
// --- Phase: checking_repository (persisted) ---
await ctx.runMutation(markRuntimeStatusRef, {
+ attempt,
repositoryCommit: result.repositoryCommit,
repositoryPath: result.repositoryPath,
runtimeId: result.runtimeId,
@@ -434,7 +459,8 @@ export const runSetup = internalAction({
correlationId: args.correlationId,
idempotencyKey: lifecycleEventIdempotencyKey(
args.projectId,
- "checking_repository"
+ "checking_repository",
+ attempt
),
organizationId: args.organizationId,
payload: {
@@ -477,7 +503,7 @@ export const runSetup = internalAction({
const readyEventId = await ctx.runMutation(appendSystemEventRef, {
actorService: "project-setup",
correlationId: args.correlationId,
- idempotencyKey: readyEventIdempotencyKey(args.projectId),
+ idempotencyKey: readyEventIdempotencyKey(args.projectId, attempt),
organizationId: args.organizationId,
payload: {
agentId: args.agentId,
@@ -512,6 +538,7 @@ export const runSetup = internalAction({
// Runtime becomes terminal only after the ready event is durably delivered.
await ctx.runMutation(markRuntimeStatusRef, {
+ attempt,
runtimeRowId,
status: "ready",
});
@@ -524,5 +551,9 @@ export const runSetup = internalAction({
};
},
});
-
-export { runtimeIdempotencyKey, readyEventIdempotencyKey };
+export {
+ failedEventIdempotencyKey,
+ lifecycleEventIdempotencyKey,
+ readyEventIdempotencyKey,
+ runtimeIdempotencyKey,
+};
diff --git a/packages/backend/convex/projectSetupQueries.ts b/packages/backend/convex/projectSetupQueries.ts
index d39c3e8..97041e5 100644
--- a/packages/backend/convex/projectSetupQueries.ts
+++ b/packages/backend/convex/projectSetupQueries.ts
@@ -25,6 +25,33 @@ export const getProjectSource = internalQuery({
},
});
+/** Authorize a retry and read its runtime within the query context. Actions do
+ * not have direct database access, so the authenticated user ID is derived by
+ * the action and verified against the project's organization here. */
+export const getRetryableRuntime = internalQuery({
+ args: { projectId: v.id("projects"), userId: v.string() },
+ handler: async (ctx, args) => {
+ const project = await ctx.db.get(args.projectId);
+ if (!project) {
+ throw new Error("Project not found");
+ }
+ const membership = await ctx.db
+ .query("organizationMembers")
+ .withIndex("by_organizationId_and_userId", (q) =>
+ q.eq("organizationId", project.organizationId).eq("userId", args.userId)
+ )
+ .unique();
+ if (!membership) {
+ throw new Error("Organization membership required");
+ }
+ const runtime = await ctx.db
+ .query("projectRuntimes")
+ .withIndex("by_projectId", (q) => q.eq("projectId", args.projectId))
+ .unique();
+ return { organizationId: project.organizationId, runtime };
+ },
+});
+
/**
* Read-only project setup status for the setup screen: latest runtime row plus
* the setup-scoped events. Auth-gated to project members.
diff --git a/packages/backend/convex/projects.ts b/packages/backend/convex/projects.ts
index b24ed15..f6f67b2 100644
--- a/packages/backend/convex/projects.ts
+++ b/packages/backend/convex/projects.ts
@@ -32,6 +32,19 @@ const runSetupRef = makeFunctionReference<
},
unknown
>("projectSetup:runSetup");
+const retryRuntimeRef = makeFunctionReference<
+ "mutation",
+ { runtimeRowId: Id<"projectRuntimes"> },
+ { retried: boolean; runtime: Doc<"projectRuntimes"> }
+>("projectRuntimes:retry");
+const getRetryableRuntimeRef = makeFunctionReference<
+ "query",
+ { projectId: Id<"projects">; userId: string },
+ {
+ organizationId: Id<"organizations">;
+ runtime: Doc<"projectRuntimes"> | null;
+ }
+>("projectSetupQueries:getRetryableRuntime");
const toProjectView = async (
ctx: Parameters[0],
@@ -241,7 +254,11 @@ export const getSetup = query({
)
)
.take(20);
- return { artifacts, events };
+ const runtime = await ctx.db
+ .query("projectRuntimes")
+ .withIndex("by_projectId", (q) => q.eq("projectId", args.projectId))
+ .unique();
+ return { artifacts, events, runtime };
},
});
@@ -345,6 +362,50 @@ export const importPublicGit = action({
return outcome;
},
});
+/**
+ * Retry a failed project setup. Authenticated and project-member scoped: only a
+ * failed ProjectRuntime is reset to "requested" with its durable attempt
+ * incremented, after which the existing `projectSetup:runSetup` coordinator is
+ * scheduled. A non-failed runtime (including an already-ready one) is left
+ * untouched and re-running is skipped. The new attempt's lifecycle/ready/failed
+ * events use distinct per-attempt idempotency keys, so they never collide with
+ * or are suppressed by prior-attempt events; history is preserved.
+ *
+ * Returns `{ retried, attempt }`: `retried` is true only when a failed runtime
+ * was actually reset; `attempt` is the runtime's current attempt number.
+ */
+export const retrySetup = action({
+ args: { projectId: v.id("projects") },
+ handler: async (
+ ctx,
+ args
+ ): Promise<{ retried: boolean; attempt: number }> => {
+ const userId = await requireAuthUserId(ctx);
+ const { organizationId, runtime } = await ctx.runQuery(
+ getRetryableRuntimeRef,
+ { projectId: args.projectId, userId }
+ );
+ if (!runtime) {
+ throw new ConvexError("Project has no setup runtime to retry");
+ }
+ const { retried, runtime: updated } = await ctx.runMutation(
+ retryRuntimeRef,
+ { runtimeRowId: runtime._id }
+ );
+ if (!retried) {
+ return { attempt: updated.attempt ?? 1, retried: false };
+ }
+ const attempt = updated.attempt ?? 1;
+ const agentId = conversationAgentId(organizationId, args.projectId);
+ await ctx.scheduler.runAfter(0, runSetupRef, {
+ agentId,
+ correlationId: `retry:${args.projectId}:attempt:${attempt}`,
+ organizationId,
+ projectId: args.projectId,
+ });
+ return { attempt, retried: true };
+ },
+});
export const updateInstructions = mutation({
args: {
diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts
index 0fa1c31..91015de 100644
--- a/packages/backend/convex/schema.ts
+++ b/packages/backend/convex/schema.ts
@@ -377,6 +377,7 @@ export default defineSchema({
"version",
]),
projectRuntimes: defineTable({
+ attempt: v.optional(v.number()),
createdAt: v.number(),
idempotencyKey: v.string(),
lastError: v.optional(v.string()),
diff --git a/packages/backend/convex/timeline.test.ts b/packages/backend/convex/timeline.test.ts
index f1830f7..2b69945 100644
--- a/packages/backend/convex/timeline.test.ts
+++ b/packages/backend/convex/timeline.test.ts
@@ -155,4 +155,59 @@ describe("timeline", () => {
setup.events.map((event: { readonly type: string }) => event.type)
).toEqual(["project.setup.blocked"]);
});
+
+ test("retries a failed runtime once while preserving stale-attempt safety", async () => {
+ const t = newTest();
+ const organization = await t
+ .withIdentity(identityA)
+ .mutation(api.organizations.ensurePersonalOrganization, {});
+ const project = await t.mutation(api.projects.persistPublicGitImport, {
+ remote: { documents: [], warnings: [] },
+ source: {
+ host: "github.com",
+ normalizedUrl: "https://github.com/example/retry-project",
+ projectName: "retry-project",
+ repositoryPath: "example/retry-project",
+ url: "https://github.com/example/retry-project.git",
+ },
+ userId: identityA.tokenIdentifier,
+ });
+ const runtimeId = await t.mutation(api.projectRuntimes.requestRuntime, {
+ idempotencyKey: `project:${project.id}:vm:v1`,
+ organizationId: organization._id,
+ projectId: project.id,
+ provider: "agentos",
+ });
+ await t.mutation(api.projectRuntimes.markStatus, {
+ attempt: 1,
+ lastError: "worker unavailable",
+ runtimeRowId: runtimeId,
+ status: "failed",
+ });
+
+ const retried = await t.mutation(api.projectRuntimes.retry, {
+ runtimeRowId: runtimeId,
+ });
+ expect(retried).toMatchObject({
+ retried: true,
+ runtime: { attempt: 2, status: "requested" },
+ });
+ expect(retried.runtime.lastError).toBeUndefined();
+
+ const staleWrite = await t.mutation(api.projectRuntimes.markStatus, {
+ attempt: 1,
+ lastError: "late failure",
+ runtimeRowId: runtimeId,
+ status: "failed",
+ });
+ expect(staleWrite).toMatchObject({ attempt: 2, status: "requested" });
+
+ const duplicateRetry = await t.mutation(api.projectRuntimes.retry, {
+ runtimeRowId: runtimeId,
+ });
+ expect(duplicateRetry).toMatchObject({
+ retried: false,
+ runtime: { attempt: 2, status: "requested" },
+ });
+ });
});
diff --git a/packages/env/src/agent.ts b/packages/env/src/agent.ts
index 2fa48fe..01d1130 100644
--- a/packages/env/src/agent.ts
+++ b/packages/env/src/agent.ts
@@ -58,31 +58,7 @@ export const env = createEnv({
{ message: "RIVET_ENDPOINT must be a valid URL or host:port" }
)
.transform((val) => (/^https?:\/\//u.test(val) ? val : `http://${val}`)),
- // Distinct endpoint the Rivet Engine invokes to reach this process's
- // in-process serverless registry handler (mounted by the Hono app at
- // RIVET_SERVERLESS_BASE_PATH). This is NOT the Engine control-plane URL
- // (that is RIVET_ENDPOINT); it is how the Engine calls back into the
- // registry handler co-hosted in this Node process. Defaults to the local
- // app listener.
- RIVET_SERVERLESS_ENDPOINT: z
- .string()
- .min(1)
- .refine(
- (val) => {
- try {
- const candidate = /^https?:\/\//u.test(val) ? val : `http://${val}`;
- void new URL(candidate);
- return true;
- } catch {
- return false;
- }
- },
- {
- message: "RIVET_SERVERLESS_ENDPOINT must be a valid URL or host:port",
- }
- )
- .transform((val) => (/^https?:\/\//u.test(val) ? val : `http://${val}`))
- .default("http://127.0.0.1:3000/internal/rivet"),
+ // Service auth between the runtime and the AgentOS actor.
RIVET_WORKSPACE_TOKEN: z.string().min(1),
},
skipValidation: process.env.SKIP_ENV_VALIDATION === "true",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fad9f60..66f0fe3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -87,9 +87,6 @@ catalogs:
react-native:
specifier: 0.86.0
version: 0.86.0
- rivetkit:
- specifier: 2.3.10
- version: 2.3.10
sonner:
specifier: 2.0.7
version: 2.0.7
@@ -110,8 +107,12 @@ catalogs:
version: 4.4.3
overrides:
+ '@rivetkit/engine-envoy-protocol': 2.3.10
+ '@rivetkit/framework-base': 2.3.10
+ '@rivetkit/react': 2.3.10
react: 19.2.8
react-dom: 19.2.8
+ rivetkit: 2.3.10
vite: npm:@voidzero-dev/vite-plus-core@0.2.2
importers:
@@ -300,7 +301,7 @@ importers:
specifier: 'catalog:'
version: 4.12.34
rivetkit:
- specifier: 'catalog:'
+ specifier: 2.3.10
version: 2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
valibot:
specifier: ^1.0.0
@@ -3608,83 +3609,46 @@ packages:
cpu: [arm64]
os: [darwin]
- '@rivetkit/engine-cli-darwin-arm64@2.3.9':
- resolution: {integrity: sha512-4mXfCo48055pnNViS+2WjC8p57eLLzLw1ZNgL0b7AoaPpxJ4YKXJ9F8TezSHtTc5ScXtPdfZb5jH4n/e6ePSMA==}
- engines: {node: '>= 20.0.0'}
- cpu: [arm64]
- os: [darwin]
-
'@rivetkit/engine-cli-darwin-x64@2.3.10':
resolution: {integrity: sha512-l5pgExB9lBt5UeNj9NOZHQv/oQCEMLuirCDAKidOU0bMD9LI2TV0S/JHWG7IWyBf34SEv+/Zo+LC4JoB8iu82Q==}
engines: {node: '>= 20.0.0'}
cpu: [x64]
os: [darwin]
- '@rivetkit/engine-cli-darwin-x64@2.3.9':
- resolution: {integrity: sha512-BiNPd5KWKFp9H3xWZqeILJ9bmz7c04fYAAwXWtqmAWfGMBCd11QAGsJFwDnoHsMQhEGpDzbYg5L0KbKUD4HIMA==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [darwin]
-
'@rivetkit/engine-cli-linux-arm64-musl@2.3.10':
resolution: {integrity: sha512-opLAOm1Up5BrYk+T3p5yHQ9e5ZtWSjVfA0LyNllYKHNyYJ+L6cxosVBf1uakyENS8ytYu92R6fhQZOUQyjgevA==}
engines: {node: '>= 20.0.0'}
cpu: [arm64]
os: [linux]
- '@rivetkit/engine-cli-linux-arm64-musl@2.3.9':
- resolution: {integrity: sha512-xbtUVXPK9aMcJgaA9XcLnZ52J9a9YXUWAP7pk9uHY7rRYODOwC8rRReiyeD++/+7+YsciwcIDcHb4daLHrN4tg==}
- engines: {node: '>= 20.0.0'}
- cpu: [arm64]
- os: [linux]
-
'@rivetkit/engine-cli-linux-x64-musl@2.3.10':
resolution: {integrity: sha512-q0WDESrssRNzVSqmnNgaLt2D8wjYhVPiiJi96kDIaatWAXvPHd7SPN4gNHdvRVUD0hImOEQDw1u3NGRyi7TBkg==}
engines: {node: '>= 20.0.0'}
cpu: [x64]
os: [linux]
- '@rivetkit/engine-cli-linux-x64-musl@2.3.9':
- resolution: {integrity: sha512-0OqFgoV4nvRJKtgXEELpwTLKuFJu6DaffG+l9xkDUdcTsf/tAgk5oNU1CLgGK0JsuT77JFucAqVd93pOEWSKhg==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [linux]
-
'@rivetkit/engine-cli-win32-x64@2.3.10':
resolution: {integrity: sha512-VnNn3R6YpMzrDauCVZRmt1L2/oUax+KQmOFSiXQwo7JPt2MdZaa5Z5ISWVD51c1/xCPiMiLDsL6V9mjpJculzw==}
engines: {node: '>= 20.0.0'}
cpu: [x64]
os: [win32]
- '@rivetkit/engine-cli-win32-x64@2.3.9':
- resolution: {integrity: sha512-Pqr6YAM49vbDUhg6olE07wvIk0AhiEhoHFDoZWxfGLnnNAbZNKEa72j7rOZa0mXMe1JPCTRfG7zG85bIH6y4tQ==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [win32]
-
'@rivetkit/engine-cli@2.3.10':
resolution: {integrity: sha512-7NoyfdT8Qk5bBOqnFgsDF2baL0BISNvWNB0mWsjA/AHRqrwg+RCQbTvkWy1iPCUr8/EdndMsNIwjxA1s8vGYwQ==}
engines: {node: '>= 20.0.0'}
- '@rivetkit/engine-cli@2.3.9':
- resolution: {integrity: sha512-6sFgD0h5Z6aTioerwCNw3WF0jRysEhJhOlQHHVdnL32KzXfZoAcJLvS3dW/M1IAKydND/MsVwSQjPSYPjTy4UQ==}
- engines: {node: '>= 20.0.0'}
-
'@rivetkit/engine-envoy-protocol@2.3.10':
resolution: {integrity: sha512-AqYekBbHrxuUOrup2oo+iSBP80ZbI1Jp8euk0rRaaxkwlnWQO3p2ymMOLHW+jaQ6zK6g4f41aSq0l1ji8SoWZA==}
- '@rivetkit/engine-envoy-protocol@2.3.9':
- resolution: {integrity: sha512-2IK41V9U8sgMnEYd/P6u/68nCufCU2dvxuHVV3uAV0uIVCdtRV8lkjuR2fSr05bwHmzSmewdqsbiNMiC/GWt8w==}
-
- '@rivetkit/framework-base@2.3.9':
- resolution: {integrity: sha512-ZSxrclYcpmdGsLMiVE2dfWNfUB6diSx+t8k4EQgL4cN02ThzJD3BM5mhU+zQVCCwfNw42eRZVIoxydYDLl/yHw==}
+ '@rivetkit/framework-base@2.3.10':
+ resolution: {integrity: sha512-lMFHdyCNf0pFdK6LlSGhiGmQoMwZevC0ayF6qYS6uDveM9fiY0SrccRr7xeosxj+/aUIDTkg0Q0w2th78dFG+g==}
'@rivetkit/on-change@6.0.1':
resolution: {integrity: sha512-QBN/KRBXLJdCgN4gBTL3XAc/zKm58atSnieXWMOyFSPmo6F1/yIVV/LTRdvAktfCttrGx7W6c32i/lwqCHWnsQ==}
engines: {node: '>=20'}
- '@rivetkit/react@2.3.9':
- resolution: {integrity: sha512-j9t82h/yIqqSt17coQZeRu3F9Q5w2FgWPDUC9fCyQUJp9PTjO7ea494PR0lSWvuEiwMRqE5JpgbHdYUQ1woE9w==}
+ '@rivetkit/react@2.3.10':
+ resolution: {integrity: sha512-CGmWf2GjznsH3NsT5sLHTtwxEJZN5QKGrZ2l7pscHS6wJ5cSzmTr4luTS2fo5+6GwFzufB4FVPqqt9O1dRnQWQ==}
peerDependencies:
react: 19.2.8
react-dom: 19.2.8
@@ -3695,24 +3659,12 @@ packages:
cpu: [arm64]
os: [darwin]
- '@rivetkit/rivetkit-napi-darwin-arm64@2.3.9':
- resolution: {integrity: sha512-3Ls1gHUeYs1mByNDgt1H3s3b9gZaXyyFrrCagoK2HrBAr0celfMa2zh0fH1BrjZufyF5oC/Jvk3ALPCFCNf4EQ==}
- engines: {node: '>= 20.0.0'}
- cpu: [arm64]
- os: [darwin]
-
'@rivetkit/rivetkit-napi-darwin-x64@2.3.10':
resolution: {integrity: sha512-U+nzDsGgrslECZ7NxoUm1vvWlO+XxXNoFuBu2eefHkm9J06vXCrSPkiY/kAFa7rhcD9UA1MsNheUxC68BmKeXg==}
engines: {node: '>= 20.0.0'}
cpu: [x64]
os: [darwin]
- '@rivetkit/rivetkit-napi-darwin-x64@2.3.9':
- resolution: {integrity: sha512-tsX60L7RXD9BnMU+0b8YnKfuAqYfH8sr6DnUAWk2z2Nnmc7E11RibVWSeny/+2LxD5Zib3f3G7Ea/Pt/NF+ypA==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [darwin]
-
'@rivetkit/rivetkit-napi-linux-arm64-gnu@2.3.10':
resolution: {integrity: sha512-2uiQOJ5CsRvB6lqhYwezRWBZxuKVs6n0aPsRCdEggLPTEUcsx43ODcVVbkbg0aDM3qE+1itWjzOu086xhqGTQg==}
engines: {node: '>= 20.0.0'}
@@ -3720,13 +3672,6 @@ packages:
os: [linux]
libc: [glibc]
- '@rivetkit/rivetkit-napi-linux-arm64-gnu@2.3.9':
- resolution: {integrity: sha512-Ov1c40CgIXMiOOo8sLLDO4cCcUw3+H/8OMOYoa7a5a5snmbJlDLSi0YYACWGRcBJvCGrjeDtQGIN3JISTL8Q9Q==}
- engines: {node: '>= 20.0.0'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
-
'@rivetkit/rivetkit-napi-linux-arm64-musl@2.3.10':
resolution: {integrity: sha512-jad0eDH9MIn2VG3Dsu+UOtQyz3X3V4sBgp7VfiqHtjtV2wqVbGnDEQbdcQK6xCDQrPNFsUbGNznzDa1C/4l3Pg==}
engines: {node: '>= 20.0.0'}
@@ -3734,13 +3679,6 @@ packages:
os: [linux]
libc: [musl]
- '@rivetkit/rivetkit-napi-linux-arm64-musl@2.3.9':
- resolution: {integrity: sha512-HgK83QYfT45HI+Ly+gWuoxWDSFzTgYJm0rFlmuOz8zweTirZ4ixHEE/lIQVU16lkHl4cO7PcRbQ1vUeQJMmVtA==}
- engines: {node: '>= 20.0.0'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
-
'@rivetkit/rivetkit-napi-linux-x64-gnu@2.3.10':
resolution: {integrity: sha512-l2wphS5wSl8xpje743/NYd9gl02FVh4qtqDQPk/LNA/WBT5WkdkkWhhTLiKiTMMA1L309XMQLH3iZbNq1yXFFg==}
engines: {node: '>= 20.0.0'}
@@ -3748,13 +3686,6 @@ packages:
os: [linux]
libc: [glibc]
- '@rivetkit/rivetkit-napi-linux-x64-gnu@2.3.9':
- resolution: {integrity: sha512-GvGfGUpCpFGWzQdA5Yey5Y5ih+J6Ay/chEwa10UlzF4MC4bX0GM//0m2FXLFUXtBjNRNdhKvAAaXWdWBuZQsSw==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
-
'@rivetkit/rivetkit-napi-linux-x64-musl@2.3.10':
resolution: {integrity: sha512-kdhVxFm8z7HWG7LWqt1TBi1ye7VDNhgqq3acsh2NlpCHPJ19aElu2yiHR2F4jh1zFZU5SpAKHJGKNbfIqGO4/Q==}
engines: {node: '>= 20.0.0'}
@@ -3762,61 +3693,30 @@ packages:
os: [linux]
libc: [musl]
- '@rivetkit/rivetkit-napi-linux-x64-musl@2.3.9':
- resolution: {integrity: sha512-J48W7iLoqE8i1n1TvhxkxFrwJTxOsdXrVf7HyXUsEyLrb+kal/aS+/ta+Bfq/veJ7wZyDw7YkOIdn0BgPinIuQ==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [linux]
- libc: [musl]
-
'@rivetkit/rivetkit-napi-win32-x64-msvc@2.3.10':
resolution: {integrity: sha512-KQdLH8sXyAKF0fEzpFeXl8AZwTObl/G4nT21BQy8B+pba2os9DUjOVy8k/bTiZzcvy8FvaL9KerfVH4Pr8yg4w==}
engines: {node: '>= 20.0.0'}
cpu: [x64]
os: [win32]
- '@rivetkit/rivetkit-napi-win32-x64-msvc@2.3.9':
- resolution: {integrity: sha512-3oZYh56m3wTVAbipHI5Rnqw1vSTI1fzoksdm1x4a45TgKi1ULxPyl2fT2E9A/Jyn1LzfTfztazN6mX0//kxmxg==}
- engines: {node: '>= 20.0.0'}
- cpu: [x64]
- os: [win32]
-
'@rivetkit/rivetkit-napi@2.3.10':
resolution: {integrity: sha512-nkaimgCcSPVZn+dGhlHMrrxVJrUwujhPlSznxehUWCaG6lR1gIysvlZucdiBTYF1iEKzzmvGqao1z25l1NVWAg==}
engines: {node: '>= 20.0.0'}
- '@rivetkit/rivetkit-napi@2.3.9':
- resolution: {integrity: sha512-lUuOK1ja6ZMwnNRsdtqJeXChbBEqyvHuI/1h5XQWHfBUu7DgX5zuyA/FJ+BY6YFWtwnkHnOVCBJeNx/3gj8Xhg==}
- engines: {node: '>= 20.0.0'}
-
'@rivetkit/rivetkit-wasm@2.3.10':
resolution: {integrity: sha512-90czUWCMQa8mAMkHg/jSUGud0jemNJeU6XHeBw5izzE3kdELo8bWBlF06rprMJV6VOwwdPfluID0FrBmZpuNgQ==}
- '@rivetkit/rivetkit-wasm@2.3.9':
- resolution: {integrity: sha512-KRCKnk0h3dvzuLHHDTKdguSCaZdDpeza4XucYi2+XZaZUJUP1zNxCHfKvQRZ1FaGhPx9rhZJTGV19+lLOWKSwA==}
-
'@rivetkit/traces@2.3.10':
resolution: {integrity: sha512-CNYz8hJOR0o77wiUQbbWh24Qj6lQk/eH0lJXfHHUisYBsjyZKnN0TlSVfhAIf7UnSMh9ASMuTwPx8s2Yz53xVA==}
engines: {node: '>=18.0.0'}
- '@rivetkit/traces@2.3.9':
- resolution: {integrity: sha512-T0og48gPh6RjIdwEuG7mEKOuxVA3R/Zcptf67z8dHVF78yuzHooeW0W0rvH6wlu4XGINWvYhGHMGY0uh4uyPEw==}
- engines: {node: '>=18.0.0'}
-
'@rivetkit/virtual-websocket@2.3.10':
resolution: {integrity: sha512-CQhhys540fASByQzbu7YqRlKlIziL9FwI/mzAS0NGg+AZMiHc6YhB/cJw/LJ1VYrEQjbm83XFkUr6i6pRnZVNw==}
- '@rivetkit/virtual-websocket@2.3.9':
- resolution: {integrity: sha512-exWO75dBB81w3ipXRg7twXY0SYQyDsB9ELJVhi1LtmWnmCn4FnHlzfinZKZlFAf5C9L0m9XAbJGS3WfRGRGlUg==}
-
'@rivetkit/workflow-engine@2.3.10':
resolution: {integrity: sha512-+1VcVsWbmCd608tgqrVndMF8jWGCNflXSXmP8NYJnq7uEBcBcQy1bkCdcD1rckAkRkfefXqfW0ReNgLv202elg==}
engines: {node: '>=18.0.0'}
- '@rivetkit/workflow-engine@2.3.9':
- resolution: {integrity: sha512-RqbXdQYc2z/PCLwOyi14iimE/+m0v7Oq7DKuzdAxs4Y1jqslUZurkn3B40pjhaNN2Kz212BJXnbmaK4YHE/q8g==}
- engines: {node: '>=18.0.0'}
-
'@rolldown/binding-android-arm64@1.1.4':
resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -8181,21 +8081,6 @@ packages:
ws:
optional: true
- rivetkit@2.3.9:
- resolution: {integrity: sha512-m8pd3+nfI81T8uxKPflOKXTvvRf6X/u5zW0vRqq6v+d/Dwh964jKAHp0pVo5R6D9PotNZQIroqy57AZvX7nc4g==}
- engines: {node: '>=22.0.0'}
- peerDependencies:
- drizzle-kit: ^0.31.2
- eventsource: ^4.0.0
- ws: ^8.0.0
- peerDependenciesMeta:
- drizzle-kit:
- optional: true
- eventsource:
- optional: true
- ws:
- optional: true
-
rolldown@1.1.4:
resolution: {integrity: sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -11233,17 +11118,17 @@ snapshots:
dependencies:
hono: 4.12.34
- '@hono/zod-openapi@1.5.1(hono@4.12.32)(zod@4.4.3)':
+ '@hono/zod-openapi@1.5.1(hono@4.12.34)(zod@4.4.3)':
dependencies:
'@asteasolutions/zod-to-openapi': 8.5.0(zod@4.4.3)
- '@hono/zod-validator': 0.9.0(hono@4.12.32)(zod@4.4.3)
- hono: 4.12.32
+ '@hono/zod-validator': 0.9.0(hono@4.12.34)(zod@4.4.3)
+ hono: 4.12.34
openapi3-ts: 4.6.1
zod: 4.4.3
- '@hono/zod-validator@0.9.0(hono@4.12.32)(zod@4.4.3)':
+ '@hono/zod-validator@0.9.0(hono@4.12.34)(zod@4.4.3)':
dependencies:
- hono: 4.12.32
+ hono: 4.12.34
zod: 4.4.3
'@humanfs/core@0.19.2':
@@ -12490,8 +12375,8 @@ snapshots:
'@agentclientprotocol/sdk': 0.16.1(zod@4.4.3)
'@agentos-software/common': 0.2.15
'@rivet-dev/agentos-core': 0.2.15(@cfworker/json-schema@4.1.1)(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(supports-color@10.2.2)(zod@4.4.3))(supports-color@10.2.2)(ws@8.21.1)
- '@rivetkit/react': 2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(ws@8.21.1)
- rivetkit: 2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
+ '@rivetkit/react': 2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(ws@8.21.1)
+ rivetkit: 2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
zod: 4.4.3
optionalDependencies:
react: 19.2.8
@@ -12542,33 +12427,18 @@ snapshots:
'@rivetkit/engine-cli-darwin-arm64@2.3.10':
optional: true
- '@rivetkit/engine-cli-darwin-arm64@2.3.9':
- optional: true
-
'@rivetkit/engine-cli-darwin-x64@2.3.10':
optional: true
- '@rivetkit/engine-cli-darwin-x64@2.3.9':
- optional: true
-
'@rivetkit/engine-cli-linux-arm64-musl@2.3.10':
optional: true
- '@rivetkit/engine-cli-linux-arm64-musl@2.3.9':
- optional: true
-
'@rivetkit/engine-cli-linux-x64-musl@2.3.10':
optional: true
- '@rivetkit/engine-cli-linux-x64-musl@2.3.9':
- optional: true
-
'@rivetkit/engine-cli-win32-x64@2.3.10':
optional: true
- '@rivetkit/engine-cli-win32-x64@2.3.9':
- optional: true
-
'@rivetkit/engine-cli@2.3.10':
optionalDependencies:
'@rivetkit/engine-cli-darwin-arm64': 2.3.10
@@ -12577,27 +12447,15 @@ snapshots:
'@rivetkit/engine-cli-linux-x64-musl': 2.3.10
'@rivetkit/engine-cli-win32-x64': 2.3.10
- '@rivetkit/engine-cli@2.3.9':
- optionalDependencies:
- '@rivetkit/engine-cli-darwin-arm64': 2.3.9
- '@rivetkit/engine-cli-darwin-x64': 2.3.9
- '@rivetkit/engine-cli-linux-arm64-musl': 2.3.9
- '@rivetkit/engine-cli-linux-x64-musl': 2.3.9
- '@rivetkit/engine-cli-win32-x64': 2.3.9
-
'@rivetkit/engine-envoy-protocol@2.3.10':
dependencies:
'@rivetkit/bare-ts': 0.6.2
- '@rivetkit/engine-envoy-protocol@2.3.9':
- dependencies:
- '@rivetkit/bare-ts': 0.6.2
-
- '@rivetkit/framework-base@2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)':
+ '@rivetkit/framework-base@2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)':
dependencies:
'@tanstack/store': 0.7.7
fast-deep-equal: 3.1.3
- rivetkit: 2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
+ rivetkit: 2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
@@ -12635,13 +12493,13 @@ snapshots:
'@rivetkit/on-change@6.0.1': {}
- '@rivetkit/react@2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(ws@8.21.1)':
+ '@rivetkit/react@2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(ws@8.21.1)':
dependencies:
- '@rivetkit/framework-base': 2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
+ '@rivetkit/framework-base': 2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
'@tanstack/react-store': 0.7.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
- rivetkit: 2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
+ rivetkit: 2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1)
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
- '@cloudflare/workers-types'
@@ -12680,45 +12538,24 @@ snapshots:
'@rivetkit/rivetkit-napi-darwin-arm64@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-darwin-arm64@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi-darwin-x64@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-darwin-x64@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi-linux-arm64-gnu@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-linux-arm64-gnu@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi-linux-arm64-musl@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-linux-arm64-musl@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi-linux-x64-gnu@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-linux-x64-gnu@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi-linux-x64-musl@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-linux-x64-musl@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi-win32-x64-msvc@2.3.10':
optional: true
- '@rivetkit/rivetkit-napi-win32-x64-msvc@2.3.9':
- optional: true
-
'@rivetkit/rivetkit-napi@2.3.10':
dependencies:
'@napi-rs/cli': 2.18.4
@@ -12732,23 +12569,8 @@ snapshots:
'@rivetkit/rivetkit-napi-linux-x64-musl': 2.3.10
'@rivetkit/rivetkit-napi-win32-x64-msvc': 2.3.10
- '@rivetkit/rivetkit-napi@2.3.9':
- dependencies:
- '@napi-rs/cli': 2.18.4
- '@rivetkit/engine-envoy-protocol': 2.3.9
- optionalDependencies:
- '@rivetkit/rivetkit-napi-darwin-arm64': 2.3.9
- '@rivetkit/rivetkit-napi-darwin-x64': 2.3.9
- '@rivetkit/rivetkit-napi-linux-arm64-gnu': 2.3.9
- '@rivetkit/rivetkit-napi-linux-arm64-musl': 2.3.9
- '@rivetkit/rivetkit-napi-linux-x64-gnu': 2.3.9
- '@rivetkit/rivetkit-napi-linux-x64-musl': 2.3.9
- '@rivetkit/rivetkit-napi-win32-x64-msvc': 2.3.9
-
'@rivetkit/rivetkit-wasm@2.3.10': {}
- '@rivetkit/rivetkit-wasm@2.3.9': {}
-
'@rivetkit/traces@2.3.10':
dependencies:
'@rivetkit/bare-ts': 0.6.2
@@ -12756,17 +12578,8 @@ snapshots:
fdb-tuple: 1.0.0
vbare: 0.0.4
- '@rivetkit/traces@2.3.9':
- dependencies:
- '@rivetkit/bare-ts': 0.6.2
- cbor-x: 1.6.5
- fdb-tuple: 1.0.0
- vbare: 0.0.4
-
'@rivetkit/virtual-websocket@2.3.10': {}
- '@rivetkit/virtual-websocket@2.3.9': {}
-
'@rivetkit/workflow-engine@2.3.10':
dependencies:
'@rivetkit/bare-ts': 0.6.2
@@ -12775,14 +12588,6 @@ snapshots:
pino: 9.14.0
vbare: 0.0.4
- '@rivetkit/workflow-engine@2.3.9':
- dependencies:
- '@rivetkit/bare-ts': 0.6.2
- cbor-x: 1.6.5
- fdb-tuple: 1.0.0
- pino: 9.14.0
- vbare: 0.0.4
-
'@rolldown/binding-android-arm64@1.1.4':
optional: true
@@ -16134,7 +15939,7 @@ snapshots:
md5.js@1.3.5:
dependencies:
- hash-base: 3.0.5
+ hash-base: 3.1.2
inherits: 2.0.4
safe-buffer: 5.2.1
@@ -17475,7 +17280,7 @@ snapshots:
rivetkit@2.3.10(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1):
dependencies:
- '@hono/zod-openapi': 1.5.1(hono@4.12.32)(zod@4.4.3)
+ '@hono/zod-openapi': 1.5.1(hono@4.12.34)(zod@4.4.3)
'@rivet-dev/agent-os-core': 0.1.1
'@rivetkit/bare-ts': 0.6.2
'@rivetkit/engine-cli': 2.3.10
@@ -17488,63 +17293,7 @@ snapshots:
'@rivetkit/workflow-engine': 2.3.10
cbor-x: 1.6.5
drizzle-orm: 0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)
- hono: 4.12.32
- invariant: 2.2.4
- p-retry: 6.2.1
- pino: 9.14.0
- uuid: 12.0.1
- vbare: 0.0.4
- zod: 4.4.3
- optionalDependencies:
- ws: 8.21.1
- transitivePeerDependencies:
- - '@aws-sdk/client-rds-data'
- - '@cloudflare/workers-types'
- - '@electric-sql/pglite'
- - '@libsql/client'
- - '@libsql/client-wasm'
- - '@neondatabase/serverless'
- - '@op-engineering/op-sqlite'
- - '@opentelemetry/api'
- - '@planetscale/database'
- - '@prisma/client'
- - '@tidbcloud/serverless'
- - '@types/better-sqlite3'
- - '@types/pg'
- - '@types/sql.js'
- - '@upstash/redis'
- - '@vercel/postgres'
- - '@xata.io/client'
- - better-sqlite3
- - bun-types
- - expo-sqlite
- - gel
- - knex
- - kysely
- - mysql2
- - pg
- - postgres
- - prisma
- - pyodide
- - sql.js
- - sqlite3
-
- rivetkit@2.3.9(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)(ws@8.21.1):
- dependencies:
- '@hono/zod-openapi': 1.5.1(hono@4.12.32)(zod@4.4.3)
- '@rivet-dev/agent-os-core': 0.1.1
- '@rivetkit/bare-ts': 0.6.2
- '@rivetkit/engine-cli': 2.3.9
- '@rivetkit/engine-envoy-protocol': 2.3.9
- '@rivetkit/on-change': 6.0.1
- '@rivetkit/rivetkit-napi': 2.3.9
- '@rivetkit/rivetkit-wasm': 2.3.9
- '@rivetkit/traces': 2.3.9
- '@rivetkit/virtual-websocket': 2.3.9
- '@rivetkit/workflow-engine': 2.3.9
- cbor-x: 1.6.5
- drizzle-orm: 0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@12.11.1)(bun-types@1.3.14)(kysely@0.29.4)
- hono: 4.12.32
+ hono: 4.12.34
invariant: 2.2.4
p-retry: 6.2.1
pino: 9.14.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 3635c8d..458827b 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -68,6 +68,10 @@ minimumReleaseAgeExclude:
- hono@4.12.34
overrides:
- react: "19.2.8"
- react-dom: "19.2.8"
- vite: "npm:@voidzero-dev/vite-plus-core@0.2.2"
+ "@rivetkit/engine-envoy-protocol": 2.3.10
+ "@rivetkit/framework-base": 2.3.10
+ "@rivetkit/react": 2.3.10
+ react: 19.2.8
+ react-dom: 19.2.8
+ rivetkit: 2.3.10
+ vite: npm:@voidzero-dev/vite-plus-core@0.2.2