diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml
index f77dce784..49ad7affc 100644
--- a/.github/workflows/publish-desktop.yml
+++ b/.github/workflows/publish-desktop.yml
@@ -107,6 +107,11 @@ jobs:
working-directory: apps/desktop
- name: Build Electron main/preload/renderer
+ env:
+ # Crash-report DSN baked into the main bundle (see the define in
+ # electron.vite.config.ts). Unset (forks, local) → crash reporting
+ # is compiled out and dumps stay local.
+ DESKTOP_SENTRY_DSN: ${{ vars.DESKTOP_SENTRY_DSN }}
run: bunx --bun electron-vite build
working-directory: apps/desktop
diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts
index 97c243c8b..9367117cc 100644
--- a/apps/desktop/electron.vite.config.ts
+++ b/apps/desktop/electron.vite.config.ts
@@ -14,11 +14,18 @@ const ELECTRON_EXTERNALS = [
"electron-store",
"electron-updater",
"electron-window-state",
+ "@sentry/electron",
+ "@sentry/electron/main",
];
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
+ define: {
+ // Crash-report DSN baked in at build time (publish-desktop.yml).
+ // Empty in local/dev builds → Sentry stays fully disabled.
+ __EXECUTOR_SENTRY_DSN__: JSON.stringify(process.env.DESKTOP_SENTRY_DSN ?? ""),
+ },
build: {
rollupOptions: {
input: { index: "src/main/index.ts" },
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 425911a8d..e9dd41d18 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -21,6 +21,8 @@
"typecheck:slow": "tsc --noEmit"
},
"dependencies": {
+ "@sentry/bun": "^10.57.0",
+ "@sentry/electron": "^7.13.0",
"electron-log": "^5",
"electron-store": "^10",
"electron-updater": "^6",
@@ -41,6 +43,7 @@
"@jitl/quickjs-wasmfile-release-sync": "catalog:",
"@modelcontextprotocol/sdk": "^1.29.0",
"@types/node": "catalog:",
+ "@zip.js/zip.js": "^2.8.26",
"bun-types": "catalog:",
"electron": "41.2.1",
"electron-builder": "^26",
diff --git a/apps/desktop/src/main/crash-screen.ts b/apps/desktop/src/main/crash-screen.ts
new file mode 100644
index 000000000..a208e1876
--- /dev/null
+++ b/apps/desktop/src/main/crash-screen.ts
@@ -0,0 +1,97 @@
+/**
+ * In-window screen shown when the sidecar dies under a running window.
+ * Replaces the dead web UI (which would otherwise sit there failing every
+ * fetch) with an explanation and a recovery path. Rendered as a data: URL
+ * in the existing BrowserWindow, so the preload bridge stays available —
+ * the buttons drive the same `window.executor` IPC the settings page uses.
+ */
+
+export interface CrashScreenOptions {
+ /** Whether a crash report was sent upstream (DSN build, not opted out). */
+ readonly reported: boolean;
+}
+
+export const sidecarCrashHtml = ({ reported }: CrashScreenOptions): string => `
+
+
+
+ Executor
+
+
+
+
+
⚠️
+
The local Executor server stopped unexpectedly
+
+ Your data is safe.${reported ? " A crash report was sent automatically so this can get fixed." : ""}
+ Restart the server to keep working.
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/apps/desktop/src/main/diagnostics.ts b/apps/desktop/src/main/diagnostics.ts
new file mode 100644
index 000000000..ea02eb2b4
--- /dev/null
+++ b/apps/desktop/src/main/diagnostics.ts
@@ -0,0 +1,281 @@
+/**
+ * Crash reporting + diagnostics export for the Electron main process.
+ *
+ * Error reporting is Sentry-backed and gated entirely on a DSN being baked
+ * in at build time (publish-desktop.yml exports DESKTOP_SENTRY_DSN; see the define
+ * in electron.vite.config.ts). Local/dev builds have no DSN, so nothing is
+ * ever sent — instead Electron's native crash reporter still writes
+ * minidumps locally so they ride along in the diagnostics zip.
+ *
+ * What reaches Sentry when enabled:
+ * - main-process uncaught exceptions / unhandled rejections
+ * - native minidumps (main, renderer, GPU) via the Crashpad integration
+ * - renderer/child process terminations (render-process-gone et al.)
+ * - sidecar crashes after a successful boot, with a stderr tail
+ *
+ * What never leaves the machine: executor data (~/.executor — data.db holds
+ * user secrets) and the desktop settings password. The diagnostics zip only
+ * packs log files, crash dumps, and a redacted manifest.
+ */
+
+import { readdirSync, statSync, writeFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { app, crashReporter, dialog, shell } from "electron";
+import log from "electron-log/main.js";
+import * as Sentry from "@sentry/electron/main";
+import { getServerSettings } from "./settings";
+
+const sentryDsn = __EXECUTOR_SENTRY_DSN__;
+
+// The informal cross-tool opt-out (consoledonottrack.com). Checked before
+// any SDK initializes, and it covers all three processes because the
+// renderer and sidecar both receive their config from this module.
+const doNotTrack =
+ process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK?.toLowerCase() === "true";
+
+export const errorReportingEnabled = sentryDsn.length > 0 && !doNotTrack;
+
+/**
+ * One id per app launch, shared by every process (main, renderer, sidecar)
+ * and stamped into the diagnostics manifest — lets a user-sent zip be
+ * matched to its Sentry events and vice versa.
+ */
+export const runId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
+
+const releaseTag = () => `executor-desktop@${app.getVersion()}`;
+const environmentTag = () => (app.isPackaged ? "production" : "development");
+
+/**
+ * Runtime crash-reporting config for the renderer (fetched over the preload
+ * bridge). The web UI is the same bundle `executor web` serves, so nothing
+ * is baked in at build time — outside the desktop app this returns null and
+ * the renderer never initializes Sentry.
+ */
+export const getCrashReportingConfig = () =>
+ errorReportingEnabled
+ ? {
+ dsn: sentryDsn,
+ release: releaseTag(),
+ environment: environmentTag(),
+ runId,
+ }
+ : null;
+
+/** Env vars handed to the sidecar so its process reports under the same id. */
+export const sidecarCrashReportingEnv = (): Record =>
+ errorReportingEnabled
+ ? {
+ EXECUTOR_SENTRY_DSN: sentryDsn,
+ EXECUTOR_SENTRY_RELEASE: releaseTag(),
+ EXECUTOR_SENTRY_ENVIRONMENT: environmentTag(),
+ EXECUTOR_RUN_ID: runId,
+ }
+ : {};
+
+/**
+ * Must run before `app.whenReady()` so the Crashpad handler attaches to
+ * every child process Electron spawns.
+ */
+export const initErrorReporting = () => {
+ if (errorReportingEnabled) {
+ Sentry.init({
+ dsn: sentryDsn,
+ release: releaseTag(),
+ environment: environmentTag(),
+ initialScope: {
+ tags: {
+ platform: process.platform,
+ arch: process.arch,
+ runId,
+ },
+ },
+ });
+ } else {
+ // No DSN baked in — keep native crash dumps local so a user-reported
+ // crash still leaves minidumps for the diagnostics zip to collect.
+ crashReporter.start({ uploadToServer: false, compress: true });
+ }
+
+ // Persist process-death signals to main.log regardless of Sentry — these
+ // are the events a "the app just disappeared" report hinges on. Sentry's
+ // ChildProcess integration reports them upstream; this keeps a local copy.
+ app.on("child-process-gone", (_event, details) => {
+ log.error("[crash] child process gone", details);
+ });
+ app.on("render-process-gone", (_event, webContents, details) => {
+ log.error("[crash] render process gone", { url: webContents.getURL(), ...details });
+ });
+
+ // Main-process uncaught errors: electron-log writes them to main.log and
+ // keeps the process alive (matching its default), Sentry (when enabled)
+ // captures them via its own integrations.
+ log.errorHandler.startCatching({ showDialog: false });
+
+ // Every log line becomes a Sentry breadcrumb, so an error event arrives
+ // with the recent log context (sidecar restarts, update checks, …) instead
+ // of a bare stack. Hooked on the file transport only so each line is
+ // recorded once. No-ops when Sentry is disabled.
+ log.hooks.push((message, transport) => {
+ if (transport !== log.transports.file) return message;
+ Sentry.addBreadcrumb({
+ category: message.scope ?? "main",
+ level: message.level === "warn" ? "warning" : message.level === "error" ? "error" : "info",
+ message: message.data
+ .map((part) => (typeof part === "string" ? part : JSON.stringify(part)))
+ .join(" ")
+ .slice(0, 1024),
+ });
+ return message;
+ });
+};
+
+/**
+ * Report a sidecar crash that happened after a successful boot. The startup
+ * path already surfaces its own dialog; this covers the "server died under
+ * a running window" case, which is otherwise invisible.
+ */
+export const reportSidecarCrash = (message: string, stderrTail: string) => {
+ // No-op when Sentry isn't initialized — captures are dropped client-side.
+ Sentry.captureMessage(message, {
+ level: "error",
+ extra: { stderrTail },
+ });
+};
+
+// ---------------------------------------------------------------------------
+// Diagnostics export — one zip in ~/Downloads a user can attach to a report.
+// ---------------------------------------------------------------------------
+
+const MAX_EXPORT_FILE_BYTES = 50 * 1024 * 1024;
+const EXPORT_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
+
+interface ZipEntry {
+ readonly name: string;
+ readonly path: string;
+}
+
+/** Recursively list files under `dir`, capped by size and age. */
+const collectFiles = (dir: string, prefix: string): ZipEntry[] => {
+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: fs probing of optional directories (crash dumps may not exist)
+ try {
+ const cutoff = Date.now() - EXPORT_MAX_AGE_MS;
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
+ const full = join(dir, entry.name);
+ if (entry.isDirectory()) return collectFiles(full, `${prefix}/${entry.name}`);
+ if (!entry.isFile()) return [];
+ const info = statSync(full);
+ if (info.size > MAX_EXPORT_FILE_BYTES) return [];
+ if (info.mtimeMs < cutoff) return [];
+ return [{ name: `${prefix}/${entry.name}`, path: full }];
+ });
+ } catch {
+ return [];
+ }
+};
+
+const exportStamp = () =>
+ new Date()
+ .toISOString()
+ .replace(/[-:]/g, "")
+ .replace(/\.\d+Z$/, "Z");
+
+const buildManifest = () => {
+ const settings = getServerSettings();
+ return {
+ generated: new Date().toISOString(),
+ app: app.getName(),
+ runId,
+ version: app.getVersion(),
+ packaged: app.isPackaged,
+ platform: process.platform,
+ arch: process.arch,
+ versions: process.versions,
+ uptimeSeconds: Math.round(process.uptime()),
+ errorReportingEnabled,
+ paths: {
+ userData: app.getPath("userData"),
+ logs: dirname(log.transports.file.getFile().path),
+ crashDumps: app.getPath("crashDumps"),
+ },
+ // Redacted on purpose: the Basic-auth password never leaves the machine.
+ serverSettings: {
+ port: settings.port,
+ requireAuth: settings.requireAuth,
+ },
+ };
+};
+
+/**
+ * Pack manifest + electron-log files + sidecar log + crash dumps into
+ * `~/Downloads/executor-diagnostics-.zip` and reveal it in the file
+ * manager. Returns the zip path.
+ */
+export const exportDiagnostics = async (): Promise => {
+ const { TextReader, Uint8ArrayReader, Uint8ArrayWriter, ZipWriter } =
+ await import("@zip.js/zip.js");
+ const { readFile } = await import("node:fs/promises");
+
+ const logsDir = dirname(log.transports.file.getFile().path);
+ const entries: ZipEntry[] = [
+ ...collectFiles(logsDir, "logs"),
+ ...collectFiles(app.getPath("crashDumps"), "crash-dumps"),
+ ];
+
+ const writer = new ZipWriter(new Uint8ArrayWriter());
+ await writer.add("manifest.json", new TextReader(JSON.stringify(buildManifest(), null, 2)));
+ for (const entry of entries) {
+ await writer.add(entry.name, new Uint8ArrayReader(new Uint8Array(await readFile(entry.path))));
+ }
+ const zipped = await writer.close();
+
+ const output = join(app.getPath("downloads"), `executor-diagnostics-${exportStamp()}.zip`);
+ writeFileSync(output, zipped);
+ log.info("[diagnostics] exported", { output, files: entries.length });
+ shell.showItemInFolder(output);
+ return output;
+};
+
+/**
+ * "Report a Problem…" menu flow: export the diagnostics zip, then open a
+ * prefilled GitHub issue. The zip is revealed in the file manager so the
+ * user can drag it onto the issue; nothing is uploaded automatically.
+ */
+export const reportAProblem = async () => {
+ await exportDiagnosticsInteractive();
+ const body = [
+ "",
+ "",
+ "",
+ "---",
+ "",
+ "| | |",
+ "|---|---|",
+ `| Version | ${app.getVersion()} |`,
+ `| OS | ${process.platform} ${process.arch} |`,
+ `| Run ID | ${runId} |`,
+ "",
+ "_A diagnostics zip was saved to your Downloads folder — please drag it into this issue._",
+ ].join("\n");
+ const url = new URL("https://github.com/RhysSullivan/executor/issues/new");
+ url.searchParams.set("title", "[desktop] ");
+ url.searchParams.set("body", body);
+ await shell.openExternal(url.toString());
+};
+
+/** Menu-item wrapper: surface failures in a dialog instead of dying silently. */
+export const exportDiagnosticsInteractive = async () => {
+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: user-initiated export surfaces failures in a native dialog
+ try {
+ await exportDiagnostics();
+ } catch (error) {
+ log.error("[diagnostics] export failed", error);
+ // oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: fs/zip failures arrive as plain Node errors and render in a native dialog
+ const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
+ await dialog.showMessageBox({
+ type: "error",
+ title: "Diagnostics export failed",
+ message: "Couldn't write the diagnostics zip.",
+ detail: `${detail.slice(0, 1200)}\n\nLogs live at: ${dirname(log.transports.file.getFile().path)}`,
+ });
+ }
+};
diff --git a/apps/desktop/src/main/global.d.ts b/apps/desktop/src/main/global.d.ts
new file mode 100644
index 000000000..49dd3f44b
--- /dev/null
+++ b/apps/desktop/src/main/global.d.ts
@@ -0,0 +1,6 @@
+/**
+ * Build-time constants injected by electron-vite's `define` (see
+ * electron.vite.config.ts). Empty string when the env var is unset —
+ * which disables Sentry entirely (local/dev builds).
+ */
+declare const __EXECUTOR_SENTRY_DSN__: string;
diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts
index 2340668c7..9c91efa01 100644
--- a/apps/desktop/src/main/index.ts
+++ b/apps/desktop/src/main/index.ts
@@ -20,9 +20,19 @@ type UpdateInfo = { readonly version: string };
import {
startSidecar,
stopSidecar,
+ onUnexpectedSidecarExit,
SidecarPortInUseError,
type SidecarConnection,
} from "./sidecar";
+import {
+ errorReportingEnabled,
+ exportDiagnostics,
+ exportDiagnosticsInteractive,
+ getCrashReportingConfig,
+ initErrorReporting,
+ reportAProblem,
+} from "./diagnostics";
+import { sidecarCrashHtml } from "./crash-screen";
import {
getServerProfiles,
getServerSettings,
@@ -47,6 +57,12 @@ app.setPath("userData", join(app.getPath("appData"), "Executor"));
log.initialize({ preload: true });
log.transports.file.level = "info";
+// Crash reporting must attach before app.whenReady() so Crashpad covers
+// every child process (renderer, GPU). Sentry-backed only when a DSN was
+// baked in at build time; otherwise dumps stay local for the diagnostics
+// export.
+initErrorReporting();
+
let mainWindow: BrowserWindow | null = null;
let connection: SidecarConnection | null = null;
let authHeaderUnsubscribe: (() => void) | null = null;
@@ -311,6 +327,12 @@ const registerIpcHandlers = () => {
setServerProfiles(value);
});
ipcMain.handle("executor:server:restart", () => restartSidecarAndReload());
+ ipcMain.handle("executor:diagnostics:export", () => exportDiagnostics());
+ ipcMain.handle("executor:crash-reporting:get", () => getCrashReportingConfig());
+ // Crash-screen escape hatch: a recurring sidecar crash may already be
+ // fixed upstream. Reuses the menu flow — staged updates prompt to install,
+ // "no updates" / failures surface in their own dialogs.
+ ipcMain.handle("executor:updates:check", () => runUpdateCheck({ alertOnFail: true }));
ipcMain.handle("executor:shell:open-external", async (_evt, rawUrl: unknown) => {
if (typeof rawUrl !== "string") return;
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: untrusted renderer string, URL ctor throws on malformed input
@@ -472,6 +494,14 @@ const installApplicationMenu = () => {
label: "Check for Updates…",
click: () => void runUpdateCheck({ alertOnFail: true }),
},
+ {
+ label: "Export Diagnostics…",
+ click: () => void exportDiagnosticsInteractive(),
+ },
+ {
+ label: "Report a Problem…",
+ click: () => void reportAProblem(),
+ },
{ type: "separator" },
...(isMac
? ([
@@ -501,6 +531,19 @@ const boot = async () => {
installApplicationMenu();
setupAutoUpdater();
registerIpcHandlers();
+ // A sidecar that dies under a live window would leave the web UI failing
+ // every request with no explanation. Swap in the crash screen — its
+ // buttons drive the regular preload bridge (restart / export diagnostics).
+ onUnexpectedSidecarExit(() => {
+ const window = liveMainWindow();
+ if (!window) return;
+ const html = sidecarCrashHtml({ reported: errorReportingEnabled });
+ void window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`);
+ // A crashing sidecar may be a broken release — quietly stage any
+ // available update so the install prompt appears on its own (same
+ // self-heal as the fatal startup path).
+ void runUpdateCheck({ alertOnFail: false });
+ });
connection = await startWithCurrentSettings();
if (!connection) {
// Port conflicts already showed their dialog inside
diff --git a/apps/desktop/src/main/sidecar.ts b/apps/desktop/src/main/sidecar.ts
index 6dc42e963..78ea39746 100644
--- a/apps/desktop/src/main/sidecar.ts
+++ b/apps/desktop/src/main/sidecar.ts
@@ -16,6 +16,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
import { homedir } from "node:os";
import { resolve, join } from "node:path";
import { app } from "electron";
+import log from "electron-log/main.js";
import { Option, Schema } from "effect";
import {
normalizeExecutorServerConnection,
@@ -23,8 +24,43 @@ import {
serializeExecutorLocalServerManifest,
} from "@executor-js/sdk/shared";
import { getServerSettings } from "./settings";
+import { reportSidecarCrash, sidecarCrashReportingEnv } from "./diagnostics";
import { SERVER_SETTINGS_USERNAME, type DesktopServerSettings } from "../shared/server-settings";
+// Sidecar output is echoed to the terminal (visible when Electron is run
+// from a shell) AND persisted to main.log under the "sidecar" scope — the
+// log file is what a user can actually send us after a crash.
+const sidecarLog = log.scope("sidecar");
+
+// Rolling stderr tail attached to crash reports. Bounded so a chatty
+// sidecar can't grow it unbounded over a long session.
+const STDERR_TAIL_LIMIT = 8 * 1024;
+
+// Children deliberately stopped via stopSidecar (quit, restart, update) —
+// their exits are expected and must not be reported as crashes.
+const expectedExits = new WeakSet();
+
+// Main/index.ts subscribes to swap the dead web UI for the in-window crash
+// screen. A callback (not an import) keeps this module free of window
+// concerns.
+let unexpectedExitListener: (() => void) | null = null;
+export const onUnexpectedSidecarExit = (listener: () => void) => {
+ unexpectedExitListener = listener;
+};
+
+/** Buffer chunked output into whole lines before handing them to `write`. */
+const makeLineSplitter = (write: (line: string) => void) => {
+ let buffer = "";
+ return (text: string) => {
+ buffer += text;
+ const lines = buffer.split("\n");
+ buffer = lines.pop() ?? "";
+ for (const line of lines) {
+ if (line.trim().length > 0) write(line);
+ }
+ };
+};
+
export interface SidecarConnection {
readonly baseUrl: string;
readonly hostname: string;
@@ -258,6 +294,10 @@ export async function startSidecar(options: StartOptions = {}): Promise sidecarLog.info(line));
+ const logStderrLine = makeLineSplitter((line) => sidecarLog.error(line));
+
const reject = (err: Error) => {
if (resolved || rejected) return;
rejected = true;
@@ -282,6 +325,7 @@ export async function startSidecar(options: StartOptions = {}): Promise {
const text = chunk.toString("utf8");
process.stdout.write(`[executor-sidecar] ${text}`);
+ logStdoutLine(text);
const match = text.match(/EXECUTOR_READY:(\d+)/);
if (match && !resolved) {
if (!child.pid) {
@@ -315,12 +359,27 @@ export async function startSidecar(options: StartOptions = {}): Promise {
const text = chunk.toString("utf8");
- stderrBuffer += text;
+ stderrBuffer = (stderrBuffer + text).slice(-STDERR_TAIL_LIMIT);
process.stderr.write(`[executor-sidecar] ${text}`);
+ logStderrLine(text);
};
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
- if (resolved || rejected) return;
+ if (resolved) {
+ // Post-boot exit: expected when we stopped it ourselves (quit,
+ // restart, update); anything else is a sidecar crash under a live
+ // window — log it and report upstream with the stderr tail.
+ if (expectedExits.has(child)) {
+ sidecarLog.info(`exited (code=${code} signal=${signal})`);
+ return;
+ }
+ const message = `Sidecar exited unexpectedly (code=${code} signal=${signal})`;
+ sidecarLog.error(message);
+ reportSidecarCrash(message, stderrBuffer);
+ unexpectedExitListener?.();
+ return;
+ }
+ if (rejected) return;
// Detect bind failure — the Node listener prints either "EADDRINUSE" or
// "address already in use" on stderr before exiting non-zero.
if (/EADDRINUSE|address already in use/i.test(stderrBuffer)) {
@@ -339,6 +398,7 @@ export async function startSidecar(options: StartOptions = {}): Promise {
+ expectedExits.add(child);
const cleanupManifest = () => {
if (!child.pid) return;
const dataDir = sidecarManifestPathByPid.get(child.pid);
diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts
index d729039ec..85ccc551c 100644
--- a/apps/desktop/src/preload/index.ts
+++ b/apps/desktop/src/preload/index.ts
@@ -40,6 +40,34 @@ const api = {
openExternal(url: string): Promise {
return ipcRenderer.invoke("executor:shell:open-external", url);
},
+ /**
+ * Pack logs + crash dumps + a redacted manifest into a zip in Downloads
+ * and reveal it in the file manager. Returns the zip path.
+ */
+ exportDiagnostics(): Promise {
+ return ipcRenderer.invoke("executor:diagnostics:export");
+ },
+ /**
+ * Run an interactive update check (menu-flow semantics: native dialogs
+ * for "update ready", "no updates", and failures). Used by the crash
+ * screen so a broken release can heal itself.
+ */
+ checkForUpdates(): Promise {
+ return ipcRenderer.invoke("executor:updates:check");
+ },
+ /**
+ * Crash-reporting config for the renderer. Null unless this desktop build
+ * shipped with a DSN baked in — the shared web UI only initializes its
+ * error reporting when this returns a config.
+ */
+ getCrashReporting(): Promise<{
+ readonly dsn: string;
+ readonly release: string;
+ readonly environment: string;
+ readonly runId: string;
+ } | null> {
+ return ipcRenderer.invoke("executor:crash-reporting:get");
+ },
} as const;
contextBridge.exposeInMainWorld("executor", api);
diff --git a/apps/desktop/src/sidecar/server.ts b/apps/desktop/src/sidecar/server.ts
index 392acd67b..99baac858 100644
--- a/apps/desktop/src/sidecar/server.ts
+++ b/apps/desktop/src/sidecar/server.ts
@@ -39,6 +39,30 @@ if (typeof Bun !== "undefined" && (await Bun.file(wasmOnDisk).exists())) {
setQuickJSModule(mod);
}
+// Crash reporting — only when the Electron main process handed us a DSN
+// (desktop builds with DESKTOP_SENTRY_DSN baked in). `executor web` and self-host
+// never set these env vars, so this stays inert everywhere else. Captures
+// uncaught exceptions / unhandled rejections in the server process; the
+// shared runId ties events to the main process and diagnostics zip.
+const sentryDsn = process.env.EXECUTOR_SENTRY_DSN;
+if (sentryDsn) {
+ const Sentry = await import("@sentry/bun");
+ Sentry.init({
+ dsn: sentryDsn,
+ release: process.env.EXECUTOR_SENTRY_RELEASE,
+ environment: process.env.EXECUTOR_SENTRY_ENVIRONMENT ?? "production",
+ tracesSampleRate: 0,
+ initialScope: {
+ tags: {
+ process: "sidecar",
+ platform: process.platform,
+ arch: process.arch,
+ ...(process.env.EXECUTOR_RUN_ID ? { runId: process.env.EXECUTOR_RUN_ID } : {}),
+ },
+ },
+ });
+}
+
import { startServer } from "@executor-js/local";
const requestedPort = parseInt(process.env.EXECUTOR_PORT ?? "0", 10);
diff --git a/bun.lock b/bun.lock
index b7cd774ec..683f73423 100644
--- a/bun.lock
+++ b/bun.lock
@@ -124,6 +124,8 @@
"name": "@executor-js/desktop",
"version": "1.5.4",
"dependencies": {
+ "@sentry/bun": "^10.57.0",
+ "@sentry/electron": "^7.13.0",
"electron-log": "^5",
"electron-store": "^10",
"electron-updater": "^6",
@@ -144,6 +146,7 @@
"@jitl/quickjs-wasmfile-release-sync": "catalog:",
"@modelcontextprotocol/sdk": "^1.29.0",
"@types/node": "catalog:",
+ "@zip.js/zip.js": "^2.8.26",
"bun-types": "catalog:",
"electron": "41.2.1",
"electron-builder": "^26",
@@ -403,6 +406,7 @@
"@executor-js/react": "workspace:*",
"@executor-js/sdk": "workspace:*",
"@executor-js/vite-plugin": "workspace:*",
+ "@sentry/browser": "^10.57.0",
"@tanstack/react-router": "catalog:",
"effect": "catalog:",
"lucide-react": "^1.7.0",
@@ -1591,6 +1595,8 @@
"@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="],
+ "@fastify/otel": ["@fastify/otel@0.18.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.28.0", "minimatch": "^10.2.4" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA=="],
+
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
@@ -1879,10 +1885,54 @@
"@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="],
+ "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w=="],
+
+ "@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.61.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q=="],
+
+ "@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.57.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FMEBChnI4FLN5TE9DHwfH7QpNir1JzXno1uz/TAucVdLCyrG0jTrKIcNHt/i30A0M2AunNBCkcd8Ei26dIPKdg=="],
+
+ "@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.31.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-f654tZFQXS5YeLDNb9KySrwtg7SnqZN119FauD7acBoTzuLduaiGTNz88ixcVSOOMGZ+EjJu/RFtx5klObC95g=="],
+
+ "@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.33.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-sCZWXGalQ01wr3tAhSR9ucqFJ0phidpAle6/17HVjD6gN8FLmZMK/8sKxdXYHy3PbnlV1P4zeiSVFNKpbFMNLA=="],
+
+ "@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.57.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-orhmlaK+ZIW9hKU+nHTbXrCSXZcH83AescTqmpamHRobRmYSQwRbD0a1odc0yAzuzOtxYiHiXAnpnIpaSSY7Ow=="],
+
+ "@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.62.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-3YNuLVPUxafXkH1jBAbGsKNsP3XVzcFDhCDCE3OqBwCwShlqQbLMRMFh1T/d5jaVZiGVmSsfof+ICKD2iOV8xg=="],
+
+ "@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.60.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w=="],
+
+ "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg=="],
+
+ "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.62.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ=="],
+
+ "@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.23.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4K+nVo+zI+aDz0Z85SObwbdixIbzS9moIuKJaYsdlzcHYnKOPtB7ya8r8Ezivy/GVIBHiKJVq4tv+BEkgOMLaQ=="],
+
+ "@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.58.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw=="],
+
+ "@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.62.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.36.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-uVip0VuGUQXZ+vFxkKxAUNq8qNl+VFlyHDh/U6IQ8COOEDfbEchdaHnpFrMYF3psZRUuoSIgb7xOeXj00RdwDA=="],
+
+ "@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.58.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-6grM3TdMyHzlGY1cUA+mwoPueB1F3dYKgKtZIH6jOFXqfHAByyLTc+6PFjGM9tKh52CFBJaDwodNlL/Td39z7Q=="],
+
+ "@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.67.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1WJp5N1lYfHq2IhECOTewFs5Tf2NfUOwQRqs/rZdXKTezArMlucxgzAaqcgp3A3YREXopXTpXHsxZTGHjNhMdQ=="],
+
+ "@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.60.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-8BahAZpKsOoc+lrZGb7Ofn4g3z8qtp5IxDfvAVpKXsEheQN7ONMH5djT5ihy6yf8yyeQJGS0gXFfpEAEeEHqQg=="],
+
+ "@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.60.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/mysql": "2.15.27" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-08pO8GFPEIz2zquKDGteBZDNmwketdgH8hTe9rVYgW9kCJXq1Psj3wPQGx+VaX4ZJKCfPeoLMYup9+cxHvZyVQ=="],
+
+ "@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.60.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@opentelemetry/sql-common": "^0.41.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-m/5d3bxQALllCzezYDk/6vajh0tj5OijMMvOZGr+qN1NMXm1dzMNwyJ0gNZW7Fo3YFRyj/jJMxIw+W7d525dlw=="],
+
+ "@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.66.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.2", "@types/pg": "8.15.6", "@types/pg-pool": "2.0.7" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA=="],
+
+ "@opentelemetry/instrumentation-redis": ["@opentelemetry/instrumentation-redis@0.62.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ=="],
+
+ "@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.33.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/tedious": "^4.0.14" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Q6WQwAD01MMTub31GlejoiFACYNw26J426wyjvU7by7fDIr2nZXNW4vhTGs7i7F0TnXBO3xN688g1tdUgYwJ5w=="],
+
"@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="],
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="],
+ "@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.38.3", "", {}, "sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw=="],
+
"@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
@@ -1895,6 +1945,8 @@
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
+ "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="],
+
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
"@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
@@ -2083,6 +2135,8 @@
"@primer/octicons": ["@primer/octicons@19.24.1", "", { "dependencies": { "object-assign": "^4.1.1" } }, "sha512-vgtSHq8IIf3oo/HjPGj0B7NkeatSyyw5mupMjXByPI1gY6uRZ/UQdv7uSJnSOCJYQJF3lVDUwiwp9wM71MYIgA=="],
+ "@prisma/instrumentation": ["@prisma/instrumentation@7.6.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ=="],
+
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
@@ -2405,19 +2459,31 @@
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
- "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.48.0", "", { "dependencies": { "@sentry/core": "10.48.0" } }, "sha512-SCiTLBXzugFKxev6NoKYBIhQoDk0gUh0AVVVepCBqfCJiWBG01Zvv0R5tCVohr4cWRllkQ8mlBdNQd/I7s9tdA=="],
+ "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.57.0", "", { "dependencies": { "@sentry/core": "10.57.0" } }, "sha512-tXObp954rMTSYKlbftjVXHtNl4t/6ssks3jkqyzmKb+PDPWzabGQO7sWwqVuTjT8Kx/8A3FmriS1bGmqxiJy3A=="],
+
+ "@sentry-internal/feedback": ["@sentry-internal/feedback@10.57.0", "", { "dependencies": { "@sentry/core": "10.57.0" } }, "sha512-ZcF4QhkqGX3iiQSXB2N0N3Awp+j5iqnDRu6PA/qyLFrWqH5ZiiAAgu59OLD9E6XAdg6iFtLYw19MAMZVK8qNOQ=="],
+
+ "@sentry-internal/replay": ["@sentry-internal/replay@10.57.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.57.0", "@sentry/core": "10.57.0" } }, "sha512-Wmnx/6ABynVH1iwuoNUqJNyjIUqsqoGML7qsyivBRKb5Wo2YQtPOQlQYfxfZSvWzGpcoSVdInkRjDssUQxQEQg=="],
- "@sentry-internal/feedback": ["@sentry-internal/feedback@10.48.0", "", { "dependencies": { "@sentry/core": "10.48.0" } }, "sha512-tGkEyOM1HDS9qebDphUMEnyk3qq/50AnuTBiFmMJyjNzowylVGmRRk0sr3xkmbVHCDXQCiYnDmSVlJ2x4SDMrQ=="],
+ "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.57.0", "", { "dependencies": { "@sentry-internal/replay": "10.57.0", "@sentry/core": "10.57.0" } }, "sha512-zsfa4JcfV0AEc9YhNxNabd5lSZL2Av84saAyexGAqcHs+67m9Gd0cGStOzMb/nCl7UAtmdP0aI+G7a3rcxxN/A=="],
- "@sentry-internal/replay": ["@sentry-internal/replay@10.48.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-sevRTePfuk4PNuz9KAKpmTZEomAU0aLXyIhOwA0OnUDdxPhkY8kq5lwDbuxTHv6DQUjUX3YgFbY45VH1JEqHKA=="],
+ "@sentry-internal/server-utils": ["@sentry-internal/server-utils@10.57.0", "", { "dependencies": { "@sentry/core": "10.57.0" } }, "sha512-Qu8ETmX/ITzteG7Im46b9HOxKKzeaIeqNvftaIlFURu1RUQdHbtGerS7QOmXzwnhuqNGNeiCQYkduB798IfRqA=="],
- "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.48.0", "", { "dependencies": { "@sentry-internal/replay": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-9nWuN2z4O+iwbTfuYV5ZmngBgJU/ZxfOo47A5RJP3Nu/kl59aJ1lUhILYOKyeNOIC/JyeERmpIcTxnlPXQzZ3Q=="],
+ "@sentry/browser": ["@sentry/browser@10.57.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.57.0", "@sentry-internal/feedback": "10.57.0", "@sentry-internal/replay": "10.57.0", "@sentry-internal/replay-canvas": "10.57.0", "@sentry/core": "10.57.0" } }, "sha512-s36AQy/CKXTfyY9Z+qUhzNomntZXgfs0rbaK7q9ffnFkqcPwzE8qQtVs58y3Suut56u+AhwSztgQtERcuZ5VIA=="],
- "@sentry/browser": ["@sentry/browser@10.48.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.48.0", "@sentry-internal/feedback": "10.48.0", "@sentry-internal/replay": "10.48.0", "@sentry-internal/replay-canvas": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-4jt2zX2ExgFcNe2x+W+/k81fmDUsOrquGtt028CiGuDuma6kEsWBI4JbooT1jhj2T+eeUxe3YGbM23Zhh7Ghhw=="],
+ "@sentry/bun": ["@sentry/bun@10.57.0", "", { "dependencies": { "@sentry/core": "10.57.0", "@sentry/node": "10.57.0" } }, "sha512-bh62U3UiXYs0Uu37DX0vfO+JdwXXG16KvMUUHi/KfQdFO5VMuCDTdtLEMt+1fEXnjOi7DfQaaItoNPkagQc52w=="],
"@sentry/cloudflare": ["@sentry/cloudflare@10.48.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/core": "10.48.0" }, "peerDependencies": { "@cloudflare/workers-types": "^4.x" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-i02Ps4/cJjFpbcHLMhNEFXTeVqLB9XpB3+/OFQ9aMFV3yDcxlvHwe0oo7WZf41iroArvpysotLG8Y8NBOU9omA=="],
- "@sentry/core": ["@sentry/core@10.48.0", "", {}, "sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g=="],
+ "@sentry/core": ["@sentry/core@10.57.0", "", {}, "sha512-kntItTA2kiT0YpL7encXaF6mkdZMB+y48lwj8w1wkfBpfJAC7sifdgrzLQZqmsqVNE3crg9VfufaAGA+78uFMg=="],
+
+ "@sentry/electron": ["@sentry/electron@7.13.0", "", { "dependencies": { "@sentry/browser": "10.50.0", "@sentry/core": "10.50.0", "@sentry/node": "10.50.0" }, "peerDependencies": { "@sentry/node-native": "10.50.0" }, "optionalPeers": ["@sentry/node-native"] }, "sha512-zW/1c9fKafCZsvhRRp9mIDH9bSvzBiIUwoN087zDDHc0vatVsqqai8nUk0bUewwYZY4Inia7r5w+kPWi8LXZzg=="],
+
+ "@sentry/node": ["@sentry/node@10.57.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/sdk-trace-base": "^2.6.1", "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry-internal/server-utils": "10.57.0", "@sentry/core": "10.57.0", "@sentry/node-core": "10.57.0", "@sentry/opentelemetry": "10.57.0", "import-in-the-middle": "^3.0.0" } }, "sha512-7KEStrJ97wPf1fA5nU5ONeTTcIIlh7oT8OMffEVA1PXmlhFoXhcQZVzr4rM+zj9tfMWT01og5Ng/Grgh3dN+FA=="],
+
+ "@sentry/node-core": ["@sentry/node-core@10.57.0", "", { "dependencies": { "@sentry/core": "10.57.0", "@sentry/opentelemetry": "10.57.0", "import-in-the-middle": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/core", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/instrumentation", "@opentelemetry/sdk-trace-base", "@opentelemetry/semantic-conventions"] }, "sha512-2v2IF6MfTiu7pimWEq2rYhZsmlwyNbs3bHUsrYFPeP/Rpa6ObDuUWPdVEzJjfyK+AqqYZYxZdV0l3+B13kTEmQ=="],
+
+ "@sentry/opentelemetry": ["@sentry/opentelemetry@10.57.0", "", { "dependencies": { "@sentry/core": "10.57.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-iwRz8cEK0GOISG34aJRO8GdYOk3nfpuT6dT2GDQrxw8f7JjkJKx9LPU8MaenOFa4MhY+Z02hI6NNcrbsoI3cXg=="],
"@sentry/react": ["@sentry/react@10.48.0", "", { "dependencies": { "@sentry/browser": "10.48.0", "@sentry/core": "10.48.0" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-uc93vKjmu6gNns+JAX4qquuxWpAMit0uGPA1TYlMjct9NG1uX3TkDPJAr9Pgd1lOXx8mKqCmj5fK33QeExMpPw=="],
@@ -2567,6 +2633,8 @@
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
+ "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
+
"@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
@@ -2661,6 +2729,8 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+ "@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
+
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
"@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="],
@@ -2669,6 +2739,8 @@
"@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
+ "@types/pg-pool": ["@types/pg-pool@2.0.7", "", { "dependencies": { "@types/pg": "*" } }, "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng=="],
+
"@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="],
"@types/prettier": ["@types/prettier@3.0.0", "", { "dependencies": { "prettier": "*" } }, "sha512-mFMBfMOz8QxhYVbuINtswBp9VL2b4Y0QqYHwqLz3YbgtfAcat2Dl6Y1o4e22S/OVE6Ebl9m7wWiMT2lSbAs1wA=="],
@@ -2681,6 +2753,8 @@
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
+ "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
+
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
@@ -2753,12 +2827,16 @@
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="],
+ "@zip.js/zip.js": ["@zip.js/zip.js@2.8.26", "", {}, "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA=="],
+
"abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
+ "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="],
+
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
@@ -3469,6 +3547,8 @@
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
+ "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
+
"fractional-indexing": ["fractional-indexing@3.2.0", "", {}, "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ=="],
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
@@ -3615,6 +3695,8 @@
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
+ "import-in-the-middle": ["import-in-the-middle@3.0.2", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-LGLYRl0A2gtyUJb2WDliBHmk6TtlHwdDjxonacZ8QrEs/ZW+YDgNv2QAfjRQWpS8HqvNcq6GGnN6jrOa5FysDQ=="],
+
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
@@ -4075,6 +4157,8 @@
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
+ "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
+
"monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="],
"motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="],
@@ -4533,6 +4617,8 @@
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+ "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="],
+
"resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="],
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
@@ -5185,6 +5271,8 @@
"@executor-js/mcporter/rolldown": ["rolldown@1.0.1", "", { "dependencies": { "@oxc-project/types": "=0.130.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.1", "@rolldown/binding-darwin-arm64": "1.0.1", "@rolldown/binding-darwin-x64": "1.0.1", "@rolldown/binding-freebsd-x64": "1.0.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", "@rolldown/binding-linux-arm64-gnu": "1.0.1", "@rolldown/binding-linux-arm64-musl": "1.0.1", "@rolldown/binding-linux-ppc64-gnu": "1.0.1", "@rolldown/binding-linux-s390x-gnu": "1.0.1", "@rolldown/binding-linux-x64-gnu": "1.0.1", "@rolldown/binding-linux-x64-musl": "1.0.1", "@rolldown/binding-openharmony-arm64": "1.0.1", "@rolldown/binding-wasm32-wasi": "1.0.1", "@rolldown/binding-win32-arm64-msvc": "1.0.1", "@rolldown/binding-win32-x64-msvc": "1.0.1" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ=="],
+ "@fastify/otel/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg=="],
+
"@graphql-tools/executor/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="],
"@graphql-tools/merge/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="],
@@ -5235,6 +5323,8 @@
"@modelcontextprotocol/sdk/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
+ "@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
+
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
"@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
@@ -5255,6 +5345,8 @@
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
+ "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="],
+
"@radix-ui/react-accordion/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-alert-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
@@ -5353,6 +5445,18 @@
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+ "@sentry/cloudflare/@sentry/core": ["@sentry/core@10.48.0", "", {}, "sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g=="],
+
+ "@sentry/electron/@sentry/browser": ["@sentry/browser@10.50.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.50.0", "@sentry-internal/feedback": "10.50.0", "@sentry-internal/replay": "10.50.0", "@sentry-internal/replay-canvas": "10.50.0", "@sentry/core": "10.50.0" } }, "sha512-1f6rAvET6myiTaSeYqvaaBwvq1LfxqWjAPIoAW/NVC9bPMkeEcuvgDajHrnZMrBeWoJ81NMyoLkyX+iOc7MoFA=="],
+
+ "@sentry/electron/@sentry/core": ["@sentry/core@10.50.0", "", {}, "sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg=="],
+
+ "@sentry/electron/@sentry/node": ["@sentry/node@10.50.0", "", { "dependencies": { "@fastify/otel": "0.18.0", "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation-amqplib": "0.61.0", "@opentelemetry/instrumentation-connect": "0.57.0", "@opentelemetry/instrumentation-dataloader": "0.31.0", "@opentelemetry/instrumentation-fs": "0.33.0", "@opentelemetry/instrumentation-generic-pool": "0.57.0", "@opentelemetry/instrumentation-graphql": "0.62.0", "@opentelemetry/instrumentation-hapi": "0.60.0", "@opentelemetry/instrumentation-http": "0.214.0", "@opentelemetry/instrumentation-ioredis": "0.62.0", "@opentelemetry/instrumentation-kafkajs": "0.23.0", "@opentelemetry/instrumentation-knex": "0.58.0", "@opentelemetry/instrumentation-koa": "0.62.0", "@opentelemetry/instrumentation-lru-memoizer": "0.58.0", "@opentelemetry/instrumentation-mongodb": "0.67.0", "@opentelemetry/instrumentation-mongoose": "0.60.0", "@opentelemetry/instrumentation-mysql": "0.60.0", "@opentelemetry/instrumentation-mysql2": "0.60.0", "@opentelemetry/instrumentation-pg": "0.66.0", "@opentelemetry/instrumentation-redis": "0.62.0", "@opentelemetry/instrumentation-tedious": "0.33.0", "@opentelemetry/sdk-trace-base": "^2.6.1", "@opentelemetry/semantic-conventions": "^1.40.0", "@prisma/instrumentation": "7.6.0", "@sentry/core": "10.50.0", "@sentry/node-core": "10.50.0", "@sentry/opentelemetry": "10.50.0", "import-in-the-middle": "^3.0.0" } }, "sha512-TvwzFQu8MGKzMQ2/tqxcNzFA8UG2kKTB+GDmA4uOzx3+GT849YZRRSJzEXCmYhk1teVd2fbmgqyYY2nyLF5a+Q=="],
+
+ "@sentry/react/@sentry/browser": ["@sentry/browser@10.48.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.48.0", "@sentry-internal/feedback": "10.48.0", "@sentry-internal/replay": "10.48.0", "@sentry-internal/replay-canvas": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-4jt2zX2ExgFcNe2x+W+/k81fmDUsOrquGtt028CiGuDuma6kEsWBI4JbooT1jhj2T+eeUxe3YGbM23Zhh7Ghhw=="],
+
+ "@sentry/react/@sentry/core": ["@sentry/core@10.48.0", "", {}, "sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g=="],
+
"@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
@@ -5389,16 +5493,22 @@
"@types/cacheable-request/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
+ "@types/connect/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
+
"@types/fs-extra/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@types/keyv/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
+ "@types/mysql/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
+
"@types/pg/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@types/plist/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@types/responselike/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
+ "@types/tedious/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
+
"@types/ws/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@types/yauzl/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
@@ -5537,6 +5647,8 @@
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+ "import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
+
"ink/es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
"ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
@@ -5879,6 +5991,10 @@
"@executor-js/mcporter/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
+ "@fastify/otel/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.212.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg=="],
+
+ "@fastify/otel/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
+
"@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -5899,6 +6015,8 @@
"@malept/flatpak-bundler/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
+ "@opentelemetry/instrumentation-pg/@types/pg/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
+
"@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="],
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
@@ -5915,6 +6033,10 @@
"@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
+ "@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.207.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ=="],
+
+ "@prisma/instrumentation/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
+
"@react-grab/cli/ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"@react-grab/cli/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
@@ -5925,6 +6047,26 @@
"@react-grab/cli/ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
+ "@sentry/electron/@sentry/browser/@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.50.0", "", { "dependencies": { "@sentry/core": "10.50.0" } }, "sha512-42bxyRTxnCmYlWnvz4CxikuQNanw8UNma2WJrtxJ0f1MAJV2GhQGSHDLnA+lvFlmiz6qct3pfen/NXGyOTegTA=="],
+
+ "@sentry/electron/@sentry/browser/@sentry-internal/feedback": ["@sentry-internal/feedback@10.50.0", "", { "dependencies": { "@sentry/core": "10.50.0" } }, "sha512-0k9XZF0wn86f77mIO2U3gNNyDZooy139CnEanRzHinrN106vVzvBZ6TUEQoHtoO1fqQxr+nWWVrqV/PXUqk47w=="],
+
+ "@sentry/electron/@sentry/browser/@sentry-internal/replay": ["@sentry-internal/replay@10.50.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.50.0", "@sentry/core": "10.50.0" } }, "sha512-51FYNfnvVLAWw1rrEWPFfwHuMRb9mkVCFGA4J9/un7SpeGBsQDziGB0Di4fsCxI7+EdSBpfLHPF0csKtCCw0oQ=="],
+
+ "@sentry/electron/@sentry/browser/@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.50.0", "", { "dependencies": { "@sentry-internal/replay": "10.50.0", "@sentry/core": "10.50.0" } }, "sha512-jx6RKBmcJSWdI92qDGS/sBv1w+7Cww879Z/moX7bw7ipHa/Ts3iDcB3rgZwvhmi17U+mvYsbJeL2DXkPo3TjPw=="],
+
+ "@sentry/electron/@sentry/node/@sentry/node-core": ["@sentry/node-core@10.50.0", "", { "dependencies": { "@sentry/core": "10.50.0", "@sentry/opentelemetry": "10.50.0", "import-in-the-middle": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/core", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/instrumentation", "@opentelemetry/sdk-trace-base", "@opentelemetry/semantic-conventions"] }, "sha512-Eb1BYf4Lc7ZYmdX3acKP6SgyGikrBA370gbGHaWI5jRu7G7vig8sIu1ghPmY5AlvqBPOetado7GniXr6fAXbTw=="],
+
+ "@sentry/electron/@sentry/node/@sentry/opentelemetry": ["@sentry/opentelemetry@10.50.0", "", { "dependencies": { "@sentry/core": "10.50.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-axn3pgDPveGdaMUC0abMCmFN7ux2pA5ebPufCef4lMIsyg7BBQvaEJ+vE19wjstMaBCAJGsdZlL3eeP2rtgRMw=="],
+
+ "@sentry/react/@sentry/browser/@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.48.0", "", { "dependencies": { "@sentry/core": "10.48.0" } }, "sha512-SCiTLBXzugFKxev6NoKYBIhQoDk0gUh0AVVVepCBqfCJiWBG01Zvv0R5tCVohr4cWRllkQ8mlBdNQd/I7s9tdA=="],
+
+ "@sentry/react/@sentry/browser/@sentry-internal/feedback": ["@sentry-internal/feedback@10.48.0", "", { "dependencies": { "@sentry/core": "10.48.0" } }, "sha512-tGkEyOM1HDS9qebDphUMEnyk3qq/50AnuTBiFmMJyjNzowylVGmRRk0sr3xkmbVHCDXQCiYnDmSVlJ2x4SDMrQ=="],
+
+ "@sentry/react/@sentry/browser/@sentry-internal/replay": ["@sentry-internal/replay@10.48.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-sevRTePfuk4PNuz9KAKpmTZEomAU0aLXyIhOwA0OnUDdxPhkY8kq5lwDbuxTHv6DQUjUX3YgFbY45VH1JEqHKA=="],
+
+ "@sentry/react/@sentry/browser/@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.48.0", "", { "dependencies": { "@sentry-internal/replay": "10.48.0", "@sentry/core": "10.48.0" } }, "sha512-9nWuN2z4O+iwbTfuYV5ZmngBgJU/ZxfOo47A5RJP3Nu/kl59aJ1lUhILYOKyeNOIC/JyeERmpIcTxnlPXQzZ3Q=="],
+
"@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-core": ["@tanstack/router-core@1.171.13", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-+NOwEj1kO/6IGmpHRIZHasYxYWpyBQGNIZAST9aNrk9Q3YlU9SgqVnl1pbLa9qAKfeNdXQIRve0RQb/0kyDeDA=="],
"@tanstack/router-cli/@tanstack/router-generator/@tanstack/router-utils": ["@tanstack/router-utils@1.162.2", "", { "dependencies": { "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ=="],
@@ -5937,16 +6079,22 @@
"@types/cacheable-request/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
+ "@types/connect/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
+
"@types/fs-extra/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/keyv/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
+ "@types/mysql/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
+
"@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/plist/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/responselike/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
+ "@types/tedious/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
+
"@types/ws/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/yauzl/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -6357,6 +6505,8 @@
"@executor-js/mcporter/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
+ "@fastify/otel/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
+
"@inquirer/core/wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
@@ -6377,6 +6527,10 @@
"@libsql/kysely-libsql/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
+ "@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
+
+ "@prisma/instrumentation/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
+
"@react-grab/cli/ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"@react-grab/cli/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
diff --git a/e2e/desktop/sidecar-crash-screen.test.ts b/e2e/desktop/sidecar-crash-screen.test.ts
new file mode 100644
index 000000000..66433064c
--- /dev/null
+++ b/e2e/desktop/sidecar-crash-screen.test.ts
@@ -0,0 +1,125 @@
+// Desktop-only: the sidecar-crash recovery flow, on camera. Launches the
+// real Electron app against a throwaway HOME, waits for the web console,
+// SIGKILLs the sidecar out from under it, asserts the in-window crash
+// screen appears, then recovers via its "Restart server" button. The
+// recording (session.mp4 + per-step screenshots) is the artifact a reviewer
+// watches; the waits are the assertions — each one times out (fails) if the
+// screen it describes never shows up.
+import { execFile } from "node:child_process";
+import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
+import { createRequire } from "node:module";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { promisify } from "node:util";
+
+import { expect } from "@effect/vitest";
+import { Effect } from "effect";
+import { _electron } from "playwright";
+
+import { scenario } from "../src/scenario";
+import { RunDir } from "../src/services";
+
+const appDir = fileURLToPath(new URL("../../apps/desktop/", import.meta.url));
+
+// require("electron") resolves to the binary path (electron's index.js
+// exports it) — resolved from apps/desktop so we get the app's pinned
+// version out of the workspace store.
+const electronBinary = createRequire(join(appDir, "package.json"))("electron") as string;
+
+scenario(
+ "Desktop · sidecar crash shows the recovery screen and restart heals it",
+ { timeout: 300_000 },
+ Effect.gen(function* () {
+ const runDir = yield* RunDir;
+ yield* Effect.promise(() => run(runDir));
+ }),
+);
+
+const run = async (runDir: string) => {
+ // Throwaway HOME = fresh ~/.executor data dir, fresh electron-store
+ // settings, no collision with a real desktop install on this machine.
+ const home = mkdtempSync(join(tmpdir(), "executor-desktop-e2e-"));
+ const videoTmp = join(runDir, ".video-tmp");
+ let stepIndex = 0;
+
+ const app = await _electron.launch({
+ executablePath: electronBinary,
+ args: [appDir],
+ cwd: appDir,
+ env: { ...process.env, HOME: home },
+ recordVideo: { dir: videoTmp, size: { width: 1280, height: 800 } },
+ timeout: 120_000,
+ });
+
+ try {
+ // firstWindow resolves only after the sidecar boots (the window is
+ // created with the server's URL) — this wait IS the boot assertion.
+ const page = await app.firstWindow({ timeout: 120_000 });
+ const step = async (label: string, body: () => Promise) => {
+ await body();
+ stepIndex += 1;
+ const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-");
+ await page.screenshot({
+ path: join(runDir, `${String(stepIndex).padStart(2, "0")}-${slug}.png`),
+ });
+ };
+
+ await step("app boots into the web console", async () => {
+ await page.getByText("Settings").first().waitFor({ timeout: 120_000 });
+ });
+
+ let sidecarPid = 0;
+ await step("the local server is killed (SIGKILL)", async () => {
+ const manifestPath = join(home, ".executor/server-control/server.json");
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as { pid: number };
+ sidecarPid = manifest.pid;
+ expect(sidecarPid, "sidecar pid recorded in the server manifest").toBeGreaterThan(0);
+ process.kill(sidecarPid, "SIGKILL");
+ await page.getByText("stopped unexpectedly").waitFor({ timeout: 30_000 });
+ });
+
+ await step("crash screen offers restart, update, and diagnostics", async () => {
+ await page.locator("#restart").waitFor({ timeout: 5_000 });
+ await page.locator("#update").waitFor({ timeout: 5_000 });
+ await page.locator("#export").waitFor({ timeout: 5_000 });
+ });
+
+ await step("restart server heals the app", async () => {
+ await page.locator("#restart").click();
+ await page.getByText("Settings").first().waitFor({ timeout: 120_000 });
+ });
+
+ const healedManifest = JSON.parse(
+ readFileSync(join(home, ".executor/server-control/server.json"), "utf8"),
+ ) as { pid: number };
+ expect(healedManifest.pid, "restarted sidecar is a new process").not.toBe(sidecarPid);
+ } finally {
+ const page = app.windows()[0];
+ const video = page?.video();
+ await app.close().catch(() => {});
+ const recordedPath = await video?.path().catch(() => undefined);
+ if (recordedPath && existsSync(recordedPath)) {
+ // mp4 plays everywhere (Safari/iOS don't do webm) — same treatment as
+ // the browser surface.
+ await promisify(execFile)("ffmpeg", [
+ "-y",
+ "-i",
+ recordedPath,
+ "-c:v",
+ "libx264",
+ "-preset",
+ "veryfast",
+ "-crf",
+ "26",
+ "-pix_fmt",
+ "yuv420p",
+ "-movflags",
+ "+faststart",
+ join(runDir, "session.mp4"),
+ ]).catch(() => {});
+ }
+ rmSync(videoTmp, { recursive: true, force: true });
+ rmSync(home, { recursive: true, force: true });
+ }
+};
diff --git a/e2e/package.json b/e2e/package.json
index c204e9a3a..6cd994567 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -10,7 +10,8 @@
"test:watch": "vitest",
"viewer:build": "bun scripts/rebuild-viewer.ts",
"serve": "bun scripts/rebuild-viewer.ts && bun scripts/serve.ts",
- "typecheck": "tsc --noEmit"
+ "typecheck": "tsc --noEmit",
+ "test:desktop": "vitest run --project desktop"
},
"dependencies": {
"@executor-js/api": "workspace:*",
diff --git a/e2e/setup/desktop.globalsetup.ts b/e2e/setup/desktop.globalsetup.ts
new file mode 100644
index 000000000..ab2d74a2d
--- /dev/null
+++ b/e2e/setup/desktop.globalsetup.ts
@@ -0,0 +1,21 @@
+// Desktop project setup: make sure the bits the Electron app loads at
+// runtime exist — the web UI bundle (served by the sidecar) and the
+// electron-vite main/preload output. Always rebuilt so a run never tests
+// stale code; both builds are incremental-fast. No server to boot: each
+// scenario launches its own app process against a throwaway HOME.
+import { execFileSync } from "node:child_process";
+import { fileURLToPath } from "node:url";
+
+const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
+const appsDesktop = fileURLToPath(new URL("../../apps/desktop/", import.meta.url));
+
+export default function setup() {
+ execFileSync("bun", ["run", "--filter", "@executor-js/local", "build"], {
+ cwd: repoRoot,
+ stdio: "inherit",
+ });
+ execFileSync("bunx", ["--bun", "electron-vite", "build"], {
+ cwd: appsDesktop,
+ stdio: "inherit",
+ });
+}
diff --git a/e2e/targets/desktop.ts b/e2e/targets/desktop.ts
new file mode 100644
index 000000000..722ca08ac
--- /dev/null
+++ b/e2e/targets/desktop.ts
@@ -0,0 +1,17 @@
+// The Electron desktop app as a target. Unlike cloud/selfhost there is no
+// long-lived instance to point a browser at — each scenario launches its own
+// app process (Playwright's electron driver) against a throwaway HOME, so
+// the target carries no capabilities and scenarios under e2e/desktop/ drive
+// the app themselves. Identity is the local OS user; there is nothing to
+// mint.
+import { Effect } from "effect";
+
+import type { Target } from "../src/target";
+
+export const desktopTarget = (): Target => ({
+ name: "desktop",
+ baseUrl: "",
+ mcpUrl: "",
+ capabilities: new Set(),
+ newIdentity: () => Effect.succeed({ label: "desktop-local-user" }),
+});
diff --git a/e2e/targets/registry.ts b/e2e/targets/registry.ts
index d1bfb0df4..342c48513 100644
--- a/e2e/targets/registry.ts
+++ b/e2e/targets/registry.ts
@@ -3,11 +3,13 @@
// vitest.config.ts + a globalsetup that boots (or attaches to) the instance.
import type { Target } from "../src/target";
import { cloudTarget } from "./cloud";
+import { desktopTarget } from "./desktop";
import { selfhostTarget } from "./selfhost";
const factories: Record Target> = {
cloud: cloudTarget,
selfhost: selfhostTarget,
+ desktop: desktopTarget,
};
let current: Target | undefined;
diff --git a/e2e/vitest.config.ts b/e2e/vitest.config.ts
index 47ab689c8..ae65a01f7 100644
--- a/e2e/vitest.config.ts
+++ b/e2e/vitest.config.ts
@@ -26,6 +26,16 @@ export default defineConfig({
// selfhost identities are the shared bootstrap admin for now — run files
// serially until per-test invite-signup isolation lands.
project("selfhost", { fileParallelism: false }),
+ // The Electron desktop app. Only desktop/** scenarios — the desktop
+ // target provides none of the standard surfaces (each scenario
+ // launches its own app via Playwright's electron driver), so running
+ // the cross-target suite here would just emit a page of skips. Needs
+ // a display; not part of the default `npm run test` chain.
+ project("desktop", {
+ include: ["desktop/**/*.test.ts"],
+ fileParallelism: false,
+ testTimeout: 300_000,
+ }),
],
},
});
diff --git a/packages/app/package.json b/packages/app/package.json
index 29d38997f..733b6ed75 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -22,6 +22,7 @@
"@executor-js/react": "workspace:*",
"@executor-js/sdk": "workspace:*",
"@executor-js/vite-plugin": "workspace:*",
+ "@sentry/browser": "^10.57.0",
"@tanstack/react-router": "catalog:",
"effect": "catalog:",
"lucide-react": "^1.7.0",
diff --git a/packages/app/src/crash-reporting.ts b/packages/app/src/crash-reporting.ts
new file mode 100644
index 000000000..e06146aa7
--- /dev/null
+++ b/packages/app/src/crash-reporting.ts
@@ -0,0 +1,54 @@
+/**
+ * Desktop-only renderer crash reporting.
+ *
+ * This bundle is served identically to `executor web`, self-host, and the
+ * desktop app, so nothing is baked in at build time. Inside the desktop app
+ * the preload bridge (`window.executor`) hands over a DSN at runtime —
+ * everywhere else the bridge is absent (or returns null in DSN-less builds)
+ * and Sentry is never imported, let alone initialized.
+ *
+ * Handled UI errors already flow through `globalThis.reportError` (see
+ * packages/react error-reporting), which Sentry's global handlers pick up
+ * once initialized — no reporter rewiring needed.
+ */
+
+interface CrashReportingConfig {
+ readonly dsn: string;
+ readonly release: string;
+ readonly environment: string;
+ readonly runId: string;
+}
+
+interface CrashReportingBridge {
+ readonly getCrashReporting?: () => Promise;
+}
+
+export const initDesktopCrashReporting = (): void => {
+ if (typeof window === "undefined") return;
+ const bridge = (window as Window & { readonly executor?: CrashReportingBridge }).executor;
+ if (typeof bridge?.getCrashReporting !== "function") return;
+ const init = async () => {
+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: crash reporting must never take the app down with it
+ try {
+ const config = await bridge.getCrashReporting?.();
+ if (!config?.dsn) return;
+ const Sentry = await import("@sentry/browser");
+ Sentry.init({
+ dsn: config.dsn,
+ release: config.release,
+ environment: config.environment,
+ sendDefaultPii: false,
+ tracesSampleRate: 0,
+ initialScope: {
+ tags: {
+ process: "renderer",
+ runId: config.runId,
+ },
+ },
+ });
+ } catch {
+ // Reporting failures stay silent — there is nowhere left to report them.
+ }
+ };
+ void init();
+};
diff --git a/packages/app/src/entry-client.tsx b/packages/app/src/entry-client.tsx
index 21ffe64d6..437785c65 100644
--- a/packages/app/src/entry-client.tsx
+++ b/packages/app/src/entry-client.tsx
@@ -2,8 +2,11 @@ import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "@tanstack/react-router";
import { getRouter } from "./router";
+import { initDesktopCrashReporting } from "./crash-reporting";
import "@executor-js/react/globals.css";
+initDesktopCrashReporting();
+
const router = getRouter();
ReactDOM.createRoot(document.getElementById("root")!).render();
diff --git a/packages/plugins/desktop-settings/src/client.tsx b/packages/plugins/desktop-settings/src/client.tsx
index 30ff386d3..05607f145 100644
--- a/packages/plugins/desktop-settings/src/client.tsx
+++ b/packages/plugins/desktop-settings/src/client.tsx
@@ -51,6 +51,8 @@ interface ExecutorBridge {
) => Promise;
readonly regeneratePassword: () => Promise;
readonly restartServer: () => Promise;
+ // Optional: present in desktop builds that ship the diagnostics export.
+ readonly exportDiagnostics?: () => Promise;
}
const readBridge = (): ExecutorBridge | null => {
@@ -133,6 +135,23 @@ function SettingsPage() {
[bridge, restartAndRefreshConnection],
);
+ const [diagnostics, setDiagnostics] = useState<
+ { readonly state: "idle" | "exporting" } | { readonly state: "done"; readonly path: string }
+ >({ state: "idle" });
+
+ const exportDiagnostics = useCallback(async () => {
+ if (!bridge?.exportDiagnostics) return;
+ setDiagnostics({ state: "exporting" });
+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: renderer ↔ Electron IPC, errors surface in the form
+ try {
+ const path = await bridge.exportDiagnostics();
+ setDiagnostics({ state: "done", path });
+ } catch {
+ setError("Diagnostics export failed — check the desktop log for details.");
+ setDiagnostics({ state: "idle" });
+ }
+ }, [bridge]);
+
const regenerate = useCallback(async () => {
if (!bridge) return;
setStatus("saving");
@@ -346,6 +365,51 @@ function SettingsPage() {
{error}
)}
+
+ {bridge.exportDiagnostics && (
+
+
Diagnostics
+
+ Packs app and server logs, crash dumps, and version info into a zip in your Downloads
+ folder — attach it when reporting a bug. Your sources, secrets, and passwords are not
+ included.
+
+
+ {/* oxlint-disable-next-line react/forbid-elements -- plugin component uses raw HTML controls per SDK convention */}
+
+ {diagnostics.state === "done" && (
+
+ {diagnostics.path}
+
+ )}
+