diff --git a/foundry/CLAUDE.md b/foundry/CLAUDE.md index 8a57e02..8af6c92 100644 --- a/foundry/CLAUDE.md +++ b/foundry/CLAUDE.md @@ -50,6 +50,11 @@ Use `pnpm` workspaces and Turborepo. - `compose.dev.yaml` loads `foundry/.env` (optional) for credentials needed by the backend (GitHub OAuth, Stripe, Daytona, API keys, etc.). - The canonical source for these credentials is `~/misc/the-foundry.env`. If `foundry/.env` does not exist, copy it: `cp ~/misc/the-foundry.env foundry/.env` - `foundry/.env` is gitignored and must never be committed. +- If your changes affect the dev server, mock server, frontend runtime, backend runtime, Vite wiring, compose files, or other server-startup/runtime behavior, you must start or restart the relevant stack before finishing the task. +- Use the matching stack for verification: + - real backend + frontend changes: `just foundry-dev` or restart with `just foundry-dev-down && just foundry-dev` + - mock frontend changes: `just foundry-mock` or restart with `just foundry-mock-down && just foundry-mock` + - local frontend-only work outside Docker: restart `pnpm --filter @sandbox-agent/foundry-frontend dev` or `just foundry-dev-mock` as appropriate - The backend does **not** hot reload. Bun's `--hot` flag causes the server to re-bind on a different port (e.g. 6421 instead of 6420), breaking all client connections while the container still exposes the original port. After backend code changes, restart the backend container: `just foundry-dev-down && just foundry-dev`. ## Railway Logs diff --git a/foundry/compose.dev.yaml b/foundry/compose.dev.yaml index 416f6c4..a66a8c6 100644 --- a/foundry/compose.dev.yaml +++ b/foundry/compose.dev.yaml @@ -84,7 +84,6 @@ services: # Use Linux-native workspace dependencies inside the container instead of host node_modules. - "foundry_node_modules:/app/node_modules" - "foundry_client_node_modules:/app/foundry/packages/client/node_modules" - - "foundry_frontend_errors_node_modules:/app/foundry/packages/frontend-errors/node_modules" - "foundry_frontend_node_modules:/app/foundry/packages/frontend/node_modules" - "foundry_shared_node_modules:/app/foundry/packages/shared/node_modules" - "foundry_pnpm_store:/tmp/.local/share/pnpm/store" @@ -100,7 +99,6 @@ volumes: foundry_rivetkit_storage: {} foundry_node_modules: {} foundry_client_node_modules: {} - foundry_frontend_errors_node_modules: {} foundry_frontend_node_modules: {} foundry_shared_node_modules: {} foundry_pnpm_store: {} diff --git a/foundry/compose.mock.yaml b/foundry/compose.mock.yaml index ffe560c..c4a06ff 100644 --- a/foundry/compose.mock.yaml +++ b/foundry/compose.mock.yaml @@ -18,7 +18,6 @@ services: - "../../../task/rivet-checkout:/task/rivet-checkout:ro" - "mock_node_modules:/app/node_modules" - "mock_client_node_modules:/app/foundry/packages/client/node_modules" - - "mock_frontend_errors_node_modules:/app/foundry/packages/frontend-errors/node_modules" - "mock_frontend_node_modules:/app/foundry/packages/frontend/node_modules" - "mock_shared_node_modules:/app/foundry/packages/shared/node_modules" - "mock_pnpm_store:/tmp/.local/share/pnpm/store" @@ -26,7 +25,6 @@ services: volumes: mock_node_modules: {} mock_client_node_modules: {} - mock_frontend_errors_node_modules: {} mock_frontend_node_modules: {} mock_shared_node_modules: {} mock_pnpm_store: {} diff --git a/foundry/docker/frontend.Dockerfile b/foundry/docker/frontend.Dockerfile index ff1ffe6..6f8eb65 100644 --- a/foundry/docker/frontend.Dockerfile +++ b/foundry/docker/frontend.Dockerfile @@ -15,7 +15,6 @@ RUN pnpm --filter @sandbox-agent/cli-shared build RUN SKIP_OPENAPI_GEN=1 pnpm --filter sandbox-agent build RUN pnpm --filter @sandbox-agent/react build RUN pnpm --filter @sandbox-agent/foundry-client build -RUN pnpm --filter @sandbox-agent/foundry-frontend-errors build ENV FOUNDRY_FRONTEND_CLIENT_MODE=remote RUN pnpm --filter @sandbox-agent/foundry-frontend build diff --git a/foundry/docker/frontend.mock.Dockerfile b/foundry/docker/frontend.mock.Dockerfile index f93f269..8e3b7be 100644 --- a/foundry/docker/frontend.mock.Dockerfile +++ b/foundry/docker/frontend.mock.Dockerfile @@ -15,7 +15,6 @@ RUN pnpm --filter @sandbox-agent/cli-shared build RUN SKIP_OPENAPI_GEN=1 pnpm --filter sandbox-agent build RUN pnpm --filter @sandbox-agent/react build RUN pnpm --filter @sandbox-agent/foundry-client build -RUN pnpm --filter @sandbox-agent/foundry-frontend-errors build ENV FOUNDRY_FRONTEND_CLIENT_MODE=mock RUN pnpm --filter @sandbox-agent/foundry-frontend build diff --git a/foundry/docker/frontend.preview.Dockerfile b/foundry/docker/frontend.preview.Dockerfile index 68f400b..05cbba7 100644 --- a/foundry/docker/frontend.preview.Dockerfile +++ b/foundry/docker/frontend.preview.Dockerfile @@ -12,7 +12,6 @@ COPY rivet-checkout /workspace/rivet-checkout RUN pnpm install --frozen-lockfile RUN pnpm --filter @sandbox-agent/foundry-shared build RUN pnpm --filter @sandbox-agent/foundry-client build -RUN pnpm --filter @sandbox-agent/foundry-frontend-errors build RUN pnpm --filter @sandbox-agent/foundry-frontend build FROM nginx:1.27-alpine diff --git a/foundry/packages/backend/src/index.ts b/foundry/packages/backend/src/index.ts index 9875c0a..cf1e6e7 100644 --- a/foundry/packages/backend/src/index.ts +++ b/foundry/packages/backend/src/index.ts @@ -9,7 +9,6 @@ import { createBackends, createNotificationService } from "./notifications/index import { createDefaultDriver } from "./driver.js"; import { createProviderRegistry } from "./providers/index.js"; import { createClient } from "rivetkit/client"; -import type { FoundryBillingPlanId } from "@sandbox-agent/foundry-shared"; import { initBetterAuthService } from "./services/better-auth.js"; import { createDefaultAppShellServices } from "./services/app-shell-runtime.js"; import { APP_SHELL_WORKSPACE_ID } from "./actors/workspace/app-shell.js"; @@ -205,23 +204,6 @@ export async function startBackend(options: BackendStartOptions = {}): Promise(action: string, run: (workspace: any) => Promise, context: AppWorkspaceLogContext = {}): Promise => { - try { - return await run(await appWorkspace({ ...context, action })); - } catch (error) { - logger.error( - { - ...context, - action, - errorMessage: error instanceof Error ? error.message : String(error), - errorStack: error instanceof Error ? error.stack : undefined, - }, - "app_workspace_action_failed", - ); - throw error; - } - }; - const requestLogContext = (c: any, sessionId?: string): AppWorkspaceLogContext => ({ ...requestHeaderContext(c), method: c.req.method, @@ -235,30 +217,6 @@ export async function startBackend(options: BackendStartOptions = {}): Promise { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.json({ - auth: { status: "signed_out", currentUserId: null }, - activeOrganizationId: null, - onboarding: { - starterRepo: { - repoFullName: "rivet-dev/sandbox-agent", - repoUrl: "https://github.com/rivet-dev/sandbox-agent", - status: "pending", - starredAt: null, - skippedAt: null, - }, - }, - users: [], - organizations: [], - }); - } - return c.json( - await appWorkspaceAction("getAppSnapshot", async (workspace) => await workspace.getAppSnapshot({ sessionId }), requestLogContext(c, sessionId)), - ); - }); - app.all("/v1/auth/*", async (c) => { return await betterAuth.auth.handler(c.req.raw); }); @@ -289,126 +247,6 @@ export async function startBackend(options: BackendStartOptions = {}): Promise { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await appWorkspaceAction("skipAppStarterRepo", async (workspace) => await workspace.skipAppStarterRepo({ sessionId }), requestLogContext(c, sessionId)), - ); - }); - - app.post("/v1/app/organizations/:organizationId/starter-repo/star", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await appWorkspaceAction( - "starAppStarterRepo", - async (workspace) => - await workspace.starAppStarterRepo({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - requestLogContext(c, sessionId), - ), - ); - }); - - app.post("/v1/app/organizations/:organizationId/select", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await appWorkspaceAction( - "selectAppOrganization", - async (workspace) => - await workspace.selectAppOrganization({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - requestLogContext(c, sessionId), - ), - ); - }); - - app.patch("/v1/app/organizations/:organizationId/profile", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - const body = await c.req.json(); - return c.json( - await appWorkspaceAction( - "updateAppOrganizationProfile", - async (workspace) => - await workspace.updateAppOrganizationProfile({ - sessionId, - organizationId: c.req.param("organizationId"), - displayName: typeof body?.displayName === "string" ? body.displayName : "", - slug: typeof body?.slug === "string" ? body.slug : "", - primaryDomain: typeof body?.primaryDomain === "string" ? body.primaryDomain : "", - }), - requestLogContext(c, sessionId), - ), - ); - }); - - app.post("/v1/app/organizations/:organizationId/import", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await appWorkspaceAction( - "triggerAppRepoImport", - async (workspace) => - await workspace.triggerAppRepoImport({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - requestLogContext(c, sessionId), - ), - ); - }); - - app.post("/v1/app/organizations/:organizationId/reconnect", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await appWorkspaceAction( - "beginAppGithubInstall", - async (workspace) => - await workspace.beginAppGithubInstall({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - requestLogContext(c, sessionId), - ), - ); - }); - - app.post("/v1/app/organizations/:organizationId/billing/checkout", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - const body = await c.req.json().catch(() => ({})); - const planId = body?.planId === "free" || body?.planId === "team" ? (body.planId as FoundryBillingPlanId) : "team"; - return c.json( - await (await appWorkspace(requestLogContext(c, sessionId))).createAppCheckoutSession({ - sessionId, - organizationId: c.req.param("organizationId"), - planId, - }), - ); - }); - app.get("/v1/billing/checkout/complete", async (c) => { const organizationId = c.req.query("organizationId"); const checkoutSessionId = c.req.query("session_id"); @@ -427,58 +265,6 @@ export async function startBackend(options: BackendStartOptions = {}): Promise { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await (await appWorkspace(requestLogContext(c, sessionId))).createAppBillingPortalSession({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - ); - }); - - app.post("/v1/app/organizations/:organizationId/billing/cancel", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await (await appWorkspace(requestLogContext(c, sessionId))).cancelAppScheduledRenewal({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - ); - }); - - app.post("/v1/app/organizations/:organizationId/billing/resume", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await (await appWorkspace(requestLogContext(c, sessionId))).resumeAppSubscription({ - sessionId, - organizationId: c.req.param("organizationId"), - }), - ); - }); - - app.post("/v1/app/workspaces/:workspaceId/seat-usage", async (c) => { - const sessionId = await resolveSessionId(c); - if (!sessionId) { - return c.text("Unauthorized", 401); - } - return c.json( - await (await appWorkspace(requestLogContext(c, sessionId))).recordAppSeatUsage({ - sessionId, - workspaceId: c.req.param("workspaceId"), - }), - ); - }); - const handleStripeWebhook = async (c: any) => { const payload = await c.req.text(); await (await appWorkspace(requestLogContext(c))).handleAppStripeWebhook({ diff --git a/foundry/packages/client/src/backend-client.ts b/foundry/packages/client/src/backend-client.ts index 6d27504..05047bb 100644 --- a/foundry/packages/client/src/backend-client.ts +++ b/foundry/packages/client/src/backend-client.ts @@ -114,6 +114,22 @@ interface WorkspaceHandle { revertWorkbenchFile(input: TaskWorkbenchDiffInput): Promise; } +interface AppWorkspaceHandle { + connect(): ActorConn; + getAppSnapshot(input: { sessionId: string }): Promise; + skipAppStarterRepo(input: { sessionId: string }): Promise; + starAppStarterRepo(input: { sessionId: string; organizationId: string }): Promise; + selectAppOrganization(input: { sessionId: string; organizationId: string }): Promise; + updateAppOrganizationProfile(input: UpdateFoundryOrganizationProfileInput & { sessionId: string }): Promise; + triggerAppRepoImport(input: { sessionId: string; organizationId: string }): Promise; + beginAppGithubInstall(input: { sessionId: string; organizationId: string }): Promise<{ url: string }>; + createAppCheckoutSession(input: { sessionId: string; organizationId: string; planId: FoundryBillingPlanId }): Promise<{ url: string }>; + createAppBillingPortalSession(input: { sessionId: string; organizationId: string }): Promise<{ url: string }>; + cancelAppScheduledRenewal(input: { sessionId: string; organizationId: string }): Promise; + resumeAppSubscription(input: { sessionId: string; organizationId: string }): Promise; + recordAppSeatUsage(input: { sessionId: string; workspaceId: string }): Promise; +} + interface TaskHandle { getTaskSummary(): Promise; getTaskDetail(): Promise; @@ -317,6 +333,24 @@ function deriveBackendEndpoints(endpoint: string): { appEndpoint: string; rivetE }; } +function signedOutAppSnapshot(): FoundryAppSnapshot { + return { + auth: { status: "signed_out", currentUserId: null }, + activeOrganizationId: null, + onboarding: { + starterRepo: { + repoFullName: "rivet-dev/sandbox-agent", + repoUrl: "https://github.com/rivet-dev/sandbox-agent", + status: "pending", + starredAt: null, + skippedAt: null, + }, + }, + users: [], + organizations: [], + }; +} + export function createBackendClient(options: BackendClientOptions): BackendClient { if (options.mode === "mock") { return createMockBackendClient(options.defaultWorkspaceId); @@ -362,11 +396,19 @@ export function createBackendClient(options: BackendClientOptions): BackendClien return (await res.json()) as T; }; - const redirectTo = async (path: string, init?: RequestInit): Promise => { - const response = await appRequest<{ url: string }>(path, init); - if (typeof window !== "undefined") { - window.location.assign(response.url); + const getSessionId = async (): Promise => { + const res = await fetch(`${appApiEndpoint}/auth/get-session`, { + credentials: "include", + }); + if (res.status === 401) { + return null; } + if (!res.ok) { + throw new Error(`auth session request failed: ${res.status} ${res.statusText}`); + } + const data = (await res.json().catch(() => null)) as { session?: { id?: string | null } | null } | null; + const sessionId = data?.session?.id; + return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null; }; const workspace = async (workspaceId: string): Promise => @@ -374,6 +416,11 @@ export function createBackendClient(options: BackendClientOptions): BackendClien createWithInput: workspaceId, }); + const appWorkspace = async (): Promise => + client.workspace.getOrCreate(workspaceKey("app"), { + createWithInput: "app", + }) as unknown as AppWorkspaceHandle; + const task = async (workspaceId: string, repoId: string, taskId: string): Promise => client.task.get(taskKey(workspaceId, repoId, taskId)); const sandboxByKey = async (workspaceId: string, providerId: ProviderId, sandboxId: string): Promise => { @@ -634,7 +681,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien if (!appSubscriptions.disposeConnPromise) { appSubscriptions.disposeConnPromise = (async () => { - const handle = await workspace("app"); + const handle = await appWorkspace(); const conn = (handle as any).connect(); const unsubscribeEvent = conn.on("appUpdated", () => { for (const currentListener of [...appSubscriptions.listeners]) { @@ -665,7 +712,11 @@ export function createBackendClient(options: BackendClientOptions): BackendClien return { async getAppSnapshot(): Promise { - return await appRequest("/app/snapshot"); + const sessionId = await getSessionId(); + if (!sessionId) { + return signedOutAppSnapshot(); + } + return await (await appWorkspace()).getAppSnapshot({ sessionId }); }, async connectWorkspace(workspaceId: string): Promise { @@ -704,75 +755,106 @@ export function createBackendClient(options: BackendClientOptions): BackendClien }, async skipAppStarterRepo(): Promise { - return await appRequest("/app/onboarding/starter-repo/skip", { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).skipAppStarterRepo({ sessionId }); }, async starAppStarterRepo(organizationId: string): Promise { - return await appRequest(`/app/organizations/${organizationId}/starter-repo/star`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).starAppStarterRepo({ sessionId, organizationId }); }, async selectAppOrganization(organizationId: string): Promise { - return await appRequest(`/app/organizations/${organizationId}/select`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).selectAppOrganization({ sessionId, organizationId }); }, async updateAppOrganizationProfile(input: UpdateFoundryOrganizationProfileInput): Promise { - return await appRequest(`/app/organizations/${input.organizationId}/profile`, { - method: "PATCH", - body: JSON.stringify({ - displayName: input.displayName, - slug: input.slug, - primaryDomain: input.primaryDomain, - }), + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).updateAppOrganizationProfile({ + sessionId, + organizationId: input.organizationId, + displayName: input.displayName, + slug: input.slug, + primaryDomain: input.primaryDomain, }); }, async triggerAppRepoImport(organizationId: string): Promise { - return await appRequest(`/app/organizations/${organizationId}/import`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).triggerAppRepoImport({ sessionId, organizationId }); }, async reconnectAppGithub(organizationId: string): Promise { - await redirectTo(`/app/organizations/${organizationId}/reconnect`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + const response = await (await appWorkspace()).beginAppGithubInstall({ sessionId, organizationId }); + if (typeof window !== "undefined") { + window.location.assign(response.url); + } }, async completeAppHostedCheckout(organizationId: string, planId: FoundryBillingPlanId): Promise { - await redirectTo(`/app/organizations/${organizationId}/billing/checkout`, { - method: "POST", - body: JSON.stringify({ planId }), - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + const response = await (await appWorkspace()).createAppCheckoutSession({ sessionId, organizationId, planId }); + if (typeof window !== "undefined") { + window.location.assign(response.url); + } }, async openAppBillingPortal(organizationId: string): Promise { - await redirectTo(`/app/organizations/${organizationId}/billing/portal`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + const response = await (await appWorkspace()).createAppBillingPortalSession({ sessionId, organizationId }); + if (typeof window !== "undefined") { + window.location.assign(response.url); + } }, async cancelAppScheduledRenewal(organizationId: string): Promise { - return await appRequest(`/app/organizations/${organizationId}/billing/cancel`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).cancelAppScheduledRenewal({ sessionId, organizationId }); }, async resumeAppSubscription(organizationId: string): Promise { - return await appRequest(`/app/organizations/${organizationId}/billing/resume`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).resumeAppSubscription({ sessionId, organizationId }); }, async recordAppSeatUsage(workspaceId: string): Promise { - return await appRequest(`/app/workspaces/${workspaceId}/seat-usage`, { - method: "POST", - }); + const sessionId = await getSessionId(); + if (!sessionId) { + throw new Error("No active auth session"); + } + return await (await appWorkspace()).recordAppSeatUsage({ sessionId, workspaceId }); }, async addRepo(workspaceId: string, remoteUrl: string): Promise { diff --git a/foundry/packages/frontend-errors/package.json b/foundry/packages/frontend-errors/package.json deleted file mode 100644 index 2104dbb..0000000 --- a/foundry/packages/frontend-errors/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@sandbox-agent/foundry-frontend-errors", - "version": "0.1.0", - "private": true, - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "./client": { - "types": "./dist/client.d.ts", - "default": "./dist/client.js" - }, - "./vite": { - "types": "./dist/vite.d.ts", - "default": "./dist/vite.js" - } - }, - "scripts": { - "build": "tsup src/index.ts src/client.ts src/vite.ts --format esm --dts", - "typecheck": "tsc --noEmit", - "test": "vitest run" - }, - "dependencies": { - "@hono/node-server": "^1.19.9", - "hono": "^4.11.9" - }, - "devDependencies": { - "tsup": "^8.5.0", - "vite": "^7.1.3" - } -} diff --git a/foundry/packages/frontend-errors/src/client.ts b/foundry/packages/frontend-errors/src/client.ts deleted file mode 100644 index 3f0044c..0000000 --- a/foundry/packages/frontend-errors/src/client.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { FrontendErrorContext } from "./types.js"; - -interface FrontendErrorCollectorGlobal { - setContext: (context: FrontendErrorContext) => void; -} - -declare global { - interface Window { - __FOUNDRY_FRONTEND_ERROR_COLLECTOR__?: FrontendErrorCollectorGlobal; - __FOUNDRY_FRONTEND_ERROR_CONTEXT__?: FrontendErrorContext; - } -} - -export function setFrontendErrorContext(context: FrontendErrorContext): void { - if (typeof window === "undefined") { - return; - } - - const nextContext = sanitizeContext(context); - window.__FOUNDRY_FRONTEND_ERROR_CONTEXT__ = { - ...(window.__FOUNDRY_FRONTEND_ERROR_CONTEXT__ ?? {}), - ...nextContext, - }; - window.__FOUNDRY_FRONTEND_ERROR_COLLECTOR__?.setContext(nextContext); -} - -function sanitizeContext(input: FrontendErrorContext): FrontendErrorContext { - const output: FrontendErrorContext = {}; - for (const [key, value] of Object.entries(input)) { - if (value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { - output[key] = value; - } - } - return output; -} diff --git a/foundry/packages/frontend-errors/src/index.ts b/foundry/packages/frontend-errors/src/index.ts deleted file mode 100644 index a07981a..0000000 --- a/foundry/packages/frontend-errors/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./router.js"; -export * from "./script.js"; -export * from "./types.js"; diff --git a/foundry/packages/frontend-errors/src/router.ts b/foundry/packages/frontend-errors/src/router.ts deleted file mode 100644 index 8060304..0000000 --- a/foundry/packages/frontend-errors/src/router.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { existsSync } from "node:fs"; -import { appendFile, mkdir } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; -import { Hono } from "hono"; -import type { FrontendErrorContext, FrontendErrorKind, FrontendErrorLogEvent } from "./types.js"; - -const DEFAULT_RELATIVE_LOG_PATH = ".foundry/logs/frontend-errors.ndjson"; -const DEFAULT_REPORTER = "foundry-frontend"; -const MAX_FIELD_LENGTH = 12_000; - -export interface FrontendErrorCollectorRouterOptions { - logFilePath?: string; - reporter?: string; -} - -export function findProjectRoot(startDirectory: string = process.cwd()): string { - let currentDirectory = resolve(startDirectory); - while (true) { - if (existsSync(join(currentDirectory, ".git"))) { - return currentDirectory; - } - const parentDirectory = dirname(currentDirectory); - if (parentDirectory === currentDirectory) { - return resolve(startDirectory); - } - currentDirectory = parentDirectory; - } -} - -export function defaultFrontendErrorLogPath(startDirectory: string = process.cwd()): string { - const root = findProjectRoot(startDirectory); - return resolve(root, DEFAULT_RELATIVE_LOG_PATH); -} - -export function createFrontendErrorCollectorRouter(options: FrontendErrorCollectorRouterOptions = {}): Hono { - const logFilePath = options.logFilePath ?? defaultFrontendErrorLogPath(); - const reporter = trimText(options.reporter, 128) ?? DEFAULT_REPORTER; - let ensureLogPathPromise: Promise | null = null; - - const app = new Hono(); - - app.get("/healthz", (c) => - c.json({ - ok: true, - logFilePath, - reporter, - }), - ); - - app.post("/events", async (c) => { - let parsedBody: unknown; - try { - parsedBody = await c.req.json(); - } catch { - return c.json({ ok: false, error: "Expected JSON body" }, 400); - } - - const inputEvents = Array.isArray(parsedBody) ? parsedBody : [parsedBody]; - if (inputEvents.length === 0) { - return c.json({ ok: false, error: "Expected at least one event" }, 400); - } - - const receivedAt = Date.now(); - const userAgent = trimText(c.req.header("user-agent"), 512); - const clientIp = readClientIp(c.req.header("x-forwarded-for")); - const normalizedEvents: FrontendErrorLogEvent[] = []; - - for (const candidate of inputEvents) { - if (!isObject(candidate)) { - continue; - } - normalizedEvents.push( - normalizeEvent({ - candidate, - reporter, - userAgent: userAgent ?? null, - clientIp: clientIp ?? null, - receivedAt, - }), - ); - } - - if (normalizedEvents.length === 0) { - return c.json({ ok: false, error: "No valid events found in request" }, 400); - } - - await ensureLogPath(); - - const payload = `${normalizedEvents.map((event) => JSON.stringify(event)).join("\n")}\n`; - await appendFile(logFilePath, payload, "utf8"); - - return c.json( - { - ok: true, - accepted: normalizedEvents.length, - }, - 202, - ); - }); - - return app; - - async function ensureLogPath(): Promise { - ensureLogPathPromise ??= mkdir(dirname(logFilePath), { recursive: true }).then(() => undefined); - await ensureLogPathPromise; - } -} - -interface NormalizeEventInput { - candidate: Record; - reporter: string; - userAgent: string | null; - clientIp: string | null; - receivedAt: number; -} - -function normalizeEvent(input: NormalizeEventInput): FrontendErrorLogEvent { - const kind = normalizeKind(input.candidate.kind); - return { - id: createEventId(), - kind, - message: trimText(input.candidate.message, MAX_FIELD_LENGTH) ?? "(no message)", - stack: trimText(input.candidate.stack, MAX_FIELD_LENGTH) ?? null, - source: trimText(input.candidate.source, 1024) ?? null, - line: normalizeNumber(input.candidate.line), - column: normalizeNumber(input.candidate.column), - url: trimText(input.candidate.url, 2048) ?? null, - timestamp: normalizeTimestamp(input.candidate.timestamp), - receivedAt: input.receivedAt, - userAgent: input.userAgent, - clientIp: input.clientIp, - reporter: input.reporter, - context: normalizeContext(input.candidate.context), - extra: normalizeExtra(input.candidate.extra), - }; -} - -function normalizeKind(value: unknown): FrontendErrorKind { - switch (value) { - case "window-error": - case "resource-error": - case "unhandled-rejection": - case "console-error": - case "fetch-error": - case "fetch-response-error": - return value; - default: - return "window-error"; - } -} - -function normalizeTimestamp(value: unknown): number { - const parsed = normalizeNumber(value); - if (parsed === null) { - return Date.now(); - } - return parsed; -} - -function normalizeNumber(value: unknown): number | null { - if (typeof value !== "number" || !Number.isFinite(value)) { - return null; - } - return value; -} - -function normalizeContext(value: unknown): FrontendErrorContext { - if (!isObject(value)) { - return {}; - } - - const context: FrontendErrorContext = {}; - for (const [key, candidate] of Object.entries(value)) { - if (!isAllowedContextValue(candidate)) { - continue; - } - const safeKey = trimText(key, 128); - if (!safeKey) { - continue; - } - if (typeof candidate === "string") { - context[safeKey] = trimText(candidate, 1024); - continue; - } - context[safeKey] = candidate; - } - - return context; -} - -function normalizeExtra(value: unknown): Record { - if (!isObject(value)) { - return {}; - } - - const normalized: Record = {}; - for (const [key, candidate] of Object.entries(value)) { - const safeKey = trimText(key, 128); - if (!safeKey) { - continue; - } - normalized[safeKey] = normalizeUnknown(candidate); - } - return normalized; -} - -function normalizeUnknown(value: unknown): unknown { - if (typeof value === "string") { - return trimText(value, 1024) ?? ""; - } - if (typeof value === "number" || typeof value === "boolean" || value === null) { - return value; - } - if (Array.isArray(value)) { - return value.slice(0, 25).map((item) => normalizeUnknown(item)); - } - if (isObject(value)) { - const output: Record = {}; - const entries = Object.entries(value).slice(0, 25); - for (const [key, candidate] of entries) { - const safeKey = trimText(key, 128); - if (!safeKey) { - continue; - } - output[safeKey] = normalizeUnknown(candidate); - } - return output; - } - return String(value); -} - -function trimText(value: unknown, maxLength: number): string | null { - if (typeof value !== "string") { - return null; - } - const trimmed = value.trim(); - if (!trimmed) { - return null; - } - if (trimmed.length <= maxLength) { - return trimmed; - } - return `${trimmed.slice(0, maxLength)}...(truncated)`; -} - -function createEventId(): string { - if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { - return crypto.randomUUID(); - } - return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; -} - -function readClientIp(forwardedFor: string | undefined): string | null { - if (!forwardedFor) { - return null; - } - const [first] = forwardedFor.split(","); - return trimText(first, 64) ?? null; -} - -function isAllowedContextValue(value: unknown): value is string | number | boolean | null | undefined { - return value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean"; -} - -function isObject(value: unknown): value is Record { - return typeof value === "object" && value !== null; -} diff --git a/foundry/packages/frontend-errors/src/script.ts b/foundry/packages/frontend-errors/src/script.ts deleted file mode 100644 index a2149b3..0000000 --- a/foundry/packages/frontend-errors/src/script.ts +++ /dev/null @@ -1,246 +0,0 @@ -import type { FrontendErrorCollectorScriptOptions } from "./types.js"; - -const DEFAULT_REPORTER = "foundry-frontend"; - -export function createFrontendErrorCollectorScript(options: FrontendErrorCollectorScriptOptions): string { - const config = { - endpoint: options.endpoint, - reporter: options.reporter ?? DEFAULT_REPORTER, - includeConsoleErrors: options.includeConsoleErrors ?? true, - includeFetchErrors: options.includeFetchErrors ?? true, - }; - - return `(function () { - if (typeof window === "undefined") { - return; - } - - if (window.__FOUNDRY_FRONTEND_ERROR_COLLECTOR__) { - return; - } - - var config = ${JSON.stringify(config)}; - var sharedContext = window.__FOUNDRY_FRONTEND_ERROR_CONTEXT__ || {}; - window.__FOUNDRY_FRONTEND_ERROR_CONTEXT__ = sharedContext; - - function now() { - return Date.now(); - } - - function clampText(input, maxLength) { - if (typeof input !== "string") { - return null; - } - var value = input.trim(); - if (!value) { - return null; - } - if (value.length <= maxLength) { - return value; - } - return value.slice(0, maxLength) + "...(truncated)"; - } - - function currentRoute() { - return location.pathname + location.search + location.hash; - } - - function safeContext() { - var copy = {}; - for (var key in sharedContext) { - if (!Object.prototype.hasOwnProperty.call(sharedContext, key)) { - continue; - } - var candidate = sharedContext[key]; - if ( - candidate === null || - candidate === undefined || - typeof candidate === "string" || - typeof candidate === "number" || - typeof candidate === "boolean" - ) { - copy[key] = candidate; - } - } - copy.route = currentRoute(); - return copy; - } - - function stringifyUnknown(input) { - if (typeof input === "string") { - return input; - } - if (input instanceof Error) { - return input.stack || input.message || String(input); - } - try { - return JSON.stringify(input); - } catch { - return String(input); - } - } - - var internalSendInFlight = false; - - function send(eventPayload) { - var payload = { - kind: eventPayload.kind || "window-error", - message: clampText(eventPayload.message || "(no message)", 12000), - stack: clampText(eventPayload.stack, 12000), - source: clampText(eventPayload.source, 1024), - line: typeof eventPayload.line === "number" ? eventPayload.line : null, - column: typeof eventPayload.column === "number" ? eventPayload.column : null, - url: clampText(eventPayload.url || location.href, 2048), - timestamp: typeof eventPayload.timestamp === "number" ? eventPayload.timestamp : now(), - context: safeContext(), - extra: eventPayload.extra || {}, - }; - - var body = JSON.stringify(payload); - - if (navigator.sendBeacon && body.length < 60000) { - var blob = new Blob([body], { type: "application/json" }); - navigator.sendBeacon(config.endpoint, blob); - return; - } - - if (internalSendInFlight) { - return; - } - - internalSendInFlight = true; - fetch(config.endpoint, { - method: "POST", - headers: { "content-type": "application/json" }, - credentials: "same-origin", - keepalive: true, - body: body, - }).catch(function () { - return; - }).finally(function () { - internalSendInFlight = false; - }); - } - - window.__FOUNDRY_FRONTEND_ERROR_COLLECTOR__ = { - setContext: function (nextContext) { - if (!nextContext || typeof nextContext !== "object") { - return; - } - for (var key in nextContext) { - if (!Object.prototype.hasOwnProperty.call(nextContext, key)) { - continue; - } - sharedContext[key] = nextContext[key]; - } - }, - }; - - if (config.includeConsoleErrors) { - var originalConsoleError = console.error.bind(console); - console.error = function () { - var message = ""; - var values = []; - for (var index = 0; index < arguments.length; index += 1) { - values.push(stringifyUnknown(arguments[index])); - } - message = values.join(" "); - send({ - kind: "console-error", - message: message || "console.error called", - timestamp: now(), - extra: { args: values.slice(0, 10) }, - }); - return originalConsoleError.apply(console, arguments); - }; - } - - window.addEventListener("error", function (event) { - var target = event.target; - var hasResourceTarget = target && target !== window && typeof target === "object"; - if (hasResourceTarget) { - var url = null; - if ("src" in target && typeof target.src === "string") { - url = target.src; - } else if ("href" in target && typeof target.href === "string") { - url = target.href; - } - send({ - kind: "resource-error", - message: "Resource failed to load", - source: event.filename || null, - line: typeof event.lineno === "number" ? event.lineno : null, - column: typeof event.colno === "number" ? event.colno : null, - url: url || location.href, - stack: null, - timestamp: now(), - }); - return; - } - - var message = clampText(event.message, 12000) || "Unhandled window error"; - var stack = event.error && event.error.stack ? String(event.error.stack) : null; - send({ - kind: "window-error", - message: message, - stack: stack, - source: event.filename || null, - line: typeof event.lineno === "number" ? event.lineno : null, - column: typeof event.colno === "number" ? event.colno : null, - url: location.href, - timestamp: now(), - }); - }, true); - - window.addEventListener("unhandledrejection", function (event) { - var reason = event.reason; - var stack = reason && reason.stack ? String(reason.stack) : null; - send({ - kind: "unhandled-rejection", - message: stringifyUnknown(reason), - stack: stack, - url: location.href, - timestamp: now(), - }); - }); - - if (config.includeFetchErrors && typeof window.fetch === "function") { - var originalFetch = window.fetch.bind(window); - window.fetch = function () { - var args = arguments; - var requestUrl = null; - if (typeof args[0] === "string") { - requestUrl = args[0]; - } else if (args[0] && typeof args[0].url === "string") { - requestUrl = args[0].url; - } - - return originalFetch.apply(window, args).then(function (response) { - if (!response.ok && response.status >= 500) { - send({ - kind: "fetch-response-error", - message: "Fetch returned HTTP " + response.status, - url: requestUrl || location.href, - timestamp: now(), - extra: { - status: response.status, - statusText: response.statusText, - }, - }); - } - return response; - }).catch(function (error) { - send({ - kind: "fetch-error", - message: stringifyUnknown(error), - stack: error && error.stack ? String(error.stack) : null, - url: requestUrl || location.href, - timestamp: now(), - }); - throw error; - }); - }; - } - -})();`; -} diff --git a/foundry/packages/frontend-errors/src/types.ts b/foundry/packages/frontend-errors/src/types.ts deleted file mode 100644 index dc7dc61..0000000 --- a/foundry/packages/frontend-errors/src/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -export type FrontendErrorKind = "window-error" | "resource-error" | "unhandled-rejection" | "console-error" | "fetch-error" | "fetch-response-error"; - -export interface FrontendErrorContext { - route?: string; - workspaceId?: string; - taskId?: string; - [key: string]: string | number | boolean | null | undefined; -} - -export interface FrontendErrorEventInput { - kind?: string; - message?: string; - stack?: string | null; - source?: string | null; - line?: number | null; - column?: number | null; - url?: string | null; - timestamp?: number; - context?: FrontendErrorContext | null; - extra?: Record | null; -} - -export interface FrontendErrorLogEvent { - id: string; - kind: FrontendErrorKind; - message: string; - stack: string | null; - source: string | null; - line: number | null; - column: number | null; - url: string | null; - timestamp: number; - receivedAt: number; - userAgent: string | null; - clientIp: string | null; - reporter: string; - context: FrontendErrorContext; - extra: Record; -} - -export interface FrontendErrorCollectorScriptOptions { - endpoint: string; - reporter?: string; - includeConsoleErrors?: boolean; - includeFetchErrors?: boolean; -} diff --git a/foundry/packages/frontend-errors/src/vite.ts b/foundry/packages/frontend-errors/src/vite.ts deleted file mode 100644 index 88fea63..0000000 --- a/foundry/packages/frontend-errors/src/vite.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { getRequestListener } from "@hono/node-server"; -import { Hono } from "hono"; -import type { Plugin } from "vite"; -import { createFrontendErrorCollectorRouter, defaultFrontendErrorLogPath } from "./router.js"; -import { createFrontendErrorCollectorScript } from "./script.js"; - -const DEFAULT_MOUNT_PATH = "/__foundry/frontend-errors"; -const DEFAULT_EVENT_PATH = "/events"; - -export interface FrontendErrorCollectorVitePluginOptions { - mountPath?: string; - logFilePath?: string; - reporter?: string; - includeConsoleErrors?: boolean; - includeFetchErrors?: boolean; -} - -export function frontendErrorCollectorVitePlugin(options: FrontendErrorCollectorVitePluginOptions = {}): Plugin { - const mountPath = normalizePath(options.mountPath ?? DEFAULT_MOUNT_PATH); - const logFilePath = options.logFilePath ?? defaultFrontendErrorLogPath(process.cwd()); - const reporter = options.reporter ?? "foundry-vite"; - const endpoint = `${mountPath}${DEFAULT_EVENT_PATH}`; - - const router = createFrontendErrorCollectorRouter({ - logFilePath, - reporter, - }); - const mountApp = new Hono().route(mountPath, router); - const listener = getRequestListener(mountApp.fetch); - - return { - name: "foundry:frontend-error-collector", - apply: "serve", - transformIndexHtml(html) { - return { - html, - tags: [ - { - tag: "script", - attrs: { type: "module" }, - children: createFrontendErrorCollectorScript({ - endpoint, - reporter, - includeConsoleErrors: options.includeConsoleErrors, - includeFetchErrors: options.includeFetchErrors, - }), - injectTo: "head-prepend", - }, - ], - }; - }, - configureServer(server) { - server.middlewares.use((req, res, next) => { - if (!req.url?.startsWith(mountPath)) { - return next(); - } - void listener(req, res).catch((error) => next(error)); - }); - }, - configurePreviewServer(server) { - server.middlewares.use((req, res, next) => { - if (!req.url?.startsWith(mountPath)) { - return next(); - } - void listener(req, res).catch((error) => next(error)); - }); - }, - }; -} - -function normalizePath(path: string): string { - if (!path.startsWith("/")) { - return `/${path}`; - } - return path.replace(/\/+$/, ""); -} diff --git a/foundry/packages/frontend-errors/test/router.test.ts b/foundry/packages/frontend-errors/test/router.test.ts deleted file mode 100644 index b246d52..0000000 --- a/foundry/packages/frontend-errors/test/router.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { mkdtemp, readFile, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { describe, expect, test } from "vitest"; -import { createFrontendErrorCollectorRouter } from "../src/router.js"; -import { createFrontendErrorCollectorScript } from "../src/script.js"; - -describe("frontend error collector router", () => { - test("writes accepted event payloads to NDJSON", async () => { - const directory = await mkdtemp(join(tmpdir(), "hf-frontend-errors-")); - const logFilePath = join(directory, "events.ndjson"); - const app = createFrontendErrorCollectorRouter({ logFilePath, reporter: "test-suite" }); - - try { - const response = await app.request("/events", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - kind: "window-error", - message: "Boom", - stack: "at app.tsx:1:1", - context: { route: "/workspaces/default" }, - }), - }); - - expect(response.status).toBe(202); - - const written = await readFile(logFilePath, "utf8"); - const [firstLine] = written.trim().split("\n"); - expect(firstLine).toBeTruthy(); - const parsed = JSON.parse(firstLine ?? "{}") as { - kind?: string; - message?: string; - reporter?: string; - context?: { route?: string }; - }; - expect(parsed.kind).toBe("window-error"); - expect(parsed.message).toBe("Boom"); - expect(parsed.reporter).toBe("test-suite"); - expect(parsed.context?.route).toBe("/workspaces/default"); - } finally { - await rm(directory, { recursive: true, force: true }); - } - }); -}); - -describe("frontend error collector script", () => { - test("embeds configured endpoint", () => { - const script = createFrontendErrorCollectorScript({ - endpoint: "/__foundry/frontend-errors/events", - }); - expect(script).toContain("/__foundry/frontend-errors/events"); - expect(script).toContain('window.addEventListener("error"'); - }); -}); diff --git a/foundry/packages/frontend-errors/tsconfig.json b/foundry/packages/frontend-errors/tsconfig.json deleted file mode 100644 index 6bb5dcd..0000000 --- a/foundry/packages/frontend-errors/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src", "test", "vitest.config.ts"] -} diff --git a/foundry/packages/frontend-errors/vitest.config.ts b/foundry/packages/frontend-errors/vitest.config.ts deleted file mode 100644 index ed8bf77..0000000 --- a/foundry/packages/frontend-errors/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - environment: "node", - include: ["test/**/*.test.ts"], - }, -}); diff --git a/foundry/packages/frontend/package.json b/foundry/packages/frontend/package.json index 7901ef4..6a2e3c4 100644 --- a/foundry/packages/frontend/package.json +++ b/foundry/packages/frontend/package.json @@ -12,7 +12,6 @@ "dependencies": { "@sandbox-agent/react": "workspace:*", "@sandbox-agent/foundry-client": "workspace:*", - "@sandbox-agent/foundry-frontend-errors": "workspace:*", "@sandbox-agent/foundry-shared": "workspace:*", "@tanstack/react-query": "^5.85.5", "@tanstack/react-router": "^1.132.23", diff --git a/foundry/packages/frontend/src/app/router.tsx b/foundry/packages/frontend/src/app/router.tsx index 343f6eb..8ee0855 100644 --- a/foundry/packages/frontend/src/app/router.tsx +++ b/foundry/packages/frontend/src/app/router.tsx @@ -1,8 +1,7 @@ import { type ReactNode, useEffect } from "react"; -import { setFrontendErrorContext } from "@sandbox-agent/foundry-frontend-errors/client"; import type { FoundryBillingPlanId } from "@sandbox-agent/foundry-shared"; import { useInterest } from "@sandbox-agent/foundry-client"; -import { Navigate, Outlet, createRootRoute, createRoute, createRouter, useRouterState } from "@tanstack/react-router"; +import { Navigate, Outlet, createRootRoute, createRoute, createRouter } from "@tanstack/react-router"; import { MockLayout } from "../components/mock-layout"; import { MockAccountSettingsPage, @@ -257,13 +256,6 @@ function WorkspaceView({ selectedTaskId: string | null; selectedSessionId: string | null; }) { - useEffect(() => { - setFrontendErrorContext({ - workspaceId, - taskId: undefined, - }); - }, [workspaceId]); - return ; } @@ -278,14 +270,6 @@ function TaskRoute() { } function TaskView({ workspaceId, taskId, sessionId }: { workspaceId: string; taskId: string; sessionId: string | null }) { - useEffect(() => { - setFrontendErrorContext({ - workspaceId, - taskId, - repoId: undefined, - }); - }, [taskId, workspaceId]); - return ; } @@ -326,13 +310,6 @@ function AppWorkspaceGate({ workspaceId, children }: { workspaceId: string; chil function RepoRouteInner({ workspaceId, repoId }: { workspaceId: string; repoId: string }) { const workspaceState = useInterest(interestManager, "workspace", { workspaceId }); - useEffect(() => { - setFrontendErrorContext({ - workspaceId, - taskId: undefined, - repoId, - }); - }, [repoId, workspaceId]); const activeTaskId = workspaceState.data?.taskSummaries.find((task) => task.repoId === repoId)?.id; if (!activeTaskId) { return ; @@ -343,22 +320,7 @@ function RepoRouteInner({ workspaceId, repoId }: { workspaceId: string; repoId: function RootLayout() { return ( <> - ); } - -function RouteContextSync() { - const location = useRouterState({ - select: (state) => state.location, - }); - - useEffect(() => { - setFrontendErrorContext({ - route: `${location.pathname}${location.search}${location.hash}`, - }); - }, [location.hash, location.pathname, location.search]); - - return null; -} diff --git a/foundry/packages/frontend/vite.config.ts b/foundry/packages/frontend/vite.config.ts index 3002a3a..399c5ff 100644 --- a/foundry/packages/frontend/vite.config.ts +++ b/foundry/packages/frontend/vite.config.ts @@ -1,7 +1,6 @@ import { defineConfig } from "vite"; import { resolve } from "node:path"; import react from "@vitejs/plugin-react"; -import { frontendErrorCollectorVitePlugin } from "@sandbox-agent/foundry-frontend-errors/vite"; const backendProxyTarget = process.env.HF_BACKEND_HTTP?.trim() || "http://127.0.0.1:7741"; const cacheDir = process.env.HF_VITE_CACHE_DIR?.trim() || undefined; @@ -9,7 +8,7 @@ export default defineConfig({ define: { "import.meta.env.FOUNDRY_FRONTEND_CLIENT_MODE": JSON.stringify(process.env.FOUNDRY_FRONTEND_CLIENT_MODE?.trim() || "remote"), }, - plugins: [react(), frontendErrorCollectorVitePlugin()], + plugins: [react()], cacheDir, resolve: { alias: { diff --git a/foundry/tsconfig.base.json b/foundry/tsconfig.base.json index 9812077..dbde20d 100644 --- a/foundry/tsconfig.base.json +++ b/foundry/tsconfig.base.json @@ -15,9 +15,7 @@ "paths": { "@sandbox-agent/foundry-client": ["packages/client/src/index.ts"], "@sandbox-agent/foundry-shared": ["packages/shared/src/index.ts"], - "@sandbox-agent/foundry-backend": ["packages/backend/src/index.ts"], - "@sandbox-agent/foundry-frontend-errors": ["packages/frontend-errors/src/index.ts"], - "@sandbox-agent/foundry-frontend-errors/*": ["packages/frontend-errors/src/*"] + "@sandbox-agent/foundry-backend": ["packages/backend/src/index.ts"] }, "noUncheckedIndexedAccess": true, "noImplicitOverride": true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a55857b..43994b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,7 +35,7 @@ importers: dependencies: '@boxlite-ai/boxlite': specifier: latest - version: 0.4.1 + version: 0.4.2 '@sandbox-agent/example-shared': specifier: workspace:* version: link:../shared @@ -554,9 +554,6 @@ importers: '@sandbox-agent/foundry-client': specifier: workspace:* version: link:../client - '@sandbox-agent/foundry-frontend-errors': - specifier: workspace:* - version: link:../frontend-errors '@sandbox-agent/foundry-shared': specifier: workspace:* version: link:../shared @@ -613,22 +610,6 @@ importers: specifier: ^7.1.3 version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2) - foundry/packages/frontend-errors: - dependencies: - '@hono/node-server': - specifier: ^1.19.9 - version: 1.19.9(hono@4.12.2) - hono: - specifier: ^4.11.9 - version: 4.12.2 - devDependencies: - tsup: - specifier: ^8.5.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - vite: - specifier: ^7.1.3 - version: 7.3.1(@types/node@25.5.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2) - foundry/packages/shared: dependencies: pino: @@ -1547,20 +1528,20 @@ packages: cpu: [x64] os: [win32] - '@boxlite-ai/boxlite-darwin-arm64@0.4.1': - resolution: {integrity: sha512-OcpQ9fGeQTBLZDOwireul0l6D+kTxs8Qmtalv/hobBUto8cOLX7NjiQ1Huo9kr0UOThu1T8nfDSNol18+iOjpw==} + '@boxlite-ai/boxlite-darwin-arm64@0.4.2': + resolution: {integrity: sha512-FwTfA8AyDXwoDb7nE7vGo04uBt2VDAiEl5leNNzroGSUKoTuCxNy8JfEbwwHb54UrIp/q7GNq7hG0JtmyxuubQ==} engines: {node: '>=18.0.0'} cpu: [arm64] os: [darwin] - '@boxlite-ai/boxlite-linux-x64-gnu@0.4.1': - resolution: {integrity: sha512-hBoZxWRvkFS8sztOjtTtgIAEE0K2xzuBtte2hl0sLfg5twgCy2BE/Ds/RikC6hMk6Ug4oc8MeBfWIhSvF70Jjw==} + '@boxlite-ai/boxlite-linux-x64-gnu@0.4.2': + resolution: {integrity: sha512-UIRiTKl1L0cx2igDiikEiBfpNbTZ0W3lft5ow7I2mkDnjtBVIQYSm+PmVXBupTYivAuPh38g9WhqJH44C1RJdQ==} engines: {node: '>=18.0.0'} cpu: [x64] os: [linux] - '@boxlite-ai/boxlite@0.4.1': - resolution: {integrity: sha512-iy302L3Yy4w8UTYQrthYzB0KGFh8y71olSUYnseUnnIQlCgHBlFHCdrdPrgUrttplBu/m4zwTRNCQq4jIzNWeg==} + '@boxlite-ai/boxlite@0.4.2': + resolution: {integrity: sha512-LVxG0feP1sBGbYz/VOm11VsU8PyUv7rvXOQJqKrfBgI9oRVyqycpY39PCJ1oC+FFho7w7d61q8VCVDlDdj8i6Q==} engines: {node: '>=18.0.0'} peerDependencies: playwright-core: '>=1.58.0' @@ -8441,16 +8422,16 @@ snapshots: '@biomejs/cli-win32-x64@2.4.6': optional: true - '@boxlite-ai/boxlite-darwin-arm64@0.4.1': + '@boxlite-ai/boxlite-darwin-arm64@0.4.2': optional: true - '@boxlite-ai/boxlite-linux-x64-gnu@0.4.1': + '@boxlite-ai/boxlite-linux-x64-gnu@0.4.2': optional: true - '@boxlite-ai/boxlite@0.4.1': + '@boxlite-ai/boxlite@0.4.2': optionalDependencies: - '@boxlite-ai/boxlite-darwin-arm64': 0.4.1 - '@boxlite-ai/boxlite-linux-x64-gnu': 0.4.1 + '@boxlite-ai/boxlite-darwin-arm64': 0.4.2 + '@boxlite-ai/boxlite-linux-x64-gnu': 0.4.2 '@bufbuild/protobuf@2.11.0': {}