diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b3f19c2..7522cf8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,11 @@ jobs: pnpm pkg set version="$VERSION" - name: Build app + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: quant-jump-pro + SENTRY_PROJECT: electron run: pnpm build - name: Create GitHub Release @@ -106,6 +111,11 @@ jobs: pnpm pkg set version="$VERSION" - name: Build app (macOS ${{ matrix.arch }}) + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: quant-jump-pro + SENTRY_PROJECT: electron run: pnpm build - name: Verify packaged inputs (macOS ${{ matrix.arch }}) @@ -173,6 +183,11 @@ jobs: pnpm pkg set version="$VERSION" - name: Build app (Windows) + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: quant-jump-pro + SENTRY_PROJECT: electron run: pnpm build - name: Verify packaged inputs (Windows) @@ -241,6 +256,11 @@ jobs: pnpm pkg set version="$VERSION" - name: Build app (Linux) + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: quant-jump-pro + SENTRY_PROJECT: electron run: pnpm build - name: Verify packaged inputs (Linux) diff --git a/README.md b/README.md index 46d0b3b8..223d6c95 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@

- Kanban Board  - Code Review  - Team View  - Claude Agent Teams UI  - Agent Comments  - Create Team  - Settings + Kanban Board  + Code Review  + Team View  + Task Detail  + Claude Agent Teams UI  + Execution Logs  + Agent Comments  + Create Team  + Settings

Claude Agent Teams UI

diff --git a/docs/screenshots/3.jpg b/docs/screenshots/3.jpg deleted file mode 100644 index 273db881..00000000 Binary files a/docs/screenshots/3.jpg and /dev/null differ diff --git a/docs/screenshots/3.png b/docs/screenshots/3.png new file mode 100644 index 00000000..d5f80a5f Binary files /dev/null and b/docs/screenshots/3.png differ diff --git a/docs/screenshots/8.png b/docs/screenshots/8.png new file mode 100644 index 00000000..5ca7abb5 Binary files /dev/null and b/docs/screenshots/8.png differ diff --git a/docs/screenshots/9.png b/docs/screenshots/9.png new file mode 100644 index 00000000..4c35de66 Binary files /dev/null and b/docs/screenshots/9.png differ diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 579bf168..cebcf716 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -1,4 +1,5 @@ import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import { sentryVitePlugin } from '@sentry/vite-plugin' import react from '@vitejs/plugin-react' import { readFileSync } from 'fs' import { resolve } from 'path' @@ -32,14 +33,36 @@ function nativeModuleStub(): Plugin { } } +// Sentry source map upload — only active in CI when SENTRY_AUTH_TOKEN is set. +const sentryPlugins = process.env.SENTRY_AUTH_TOKEN + ? [ + sentryVitePlugin({ + org: process.env.SENTRY_ORG ?? 'quant-jump-pro', + project: process.env.SENTRY_PROJECT ?? 'electron', + authToken: process.env.SENTRY_AUTH_TOKEN, + release: { name: `claude-agent-teams-ui@${pkg.version}` }, + sourcemaps: { + filesToDeleteAfterUpload: ['./out/renderer/**/*.map', './dist-electron/**/*.map'], + }, + }), + ] + : [] + export default defineConfig({ main: { plugins: [ externalizeDepsPlugin({ exclude: bundledDeps }), - nativeModuleStub() + nativeModuleStub(), + ...sentryPlugins, ], + define: { + __APP_VERSION__: JSON.stringify(pkg.version), + // Inject DSN at compile time — process.env.SENTRY_DSN is NOT available + // at runtime in packaged Electron apps (only during CI build). + 'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN ?? ''), + }, resolve: { alias: { '@main': resolve(__dirname, 'src/main'), @@ -48,6 +71,7 @@ export default defineConfig({ } }, build: { + sourcemap: 'hidden', outDir: 'dist-electron/main', rollupOptions: { input: { @@ -94,6 +118,11 @@ export default defineConfig({ optimizeDeps: { include: ['@codemirror/language-data'] }, + define: { + __APP_VERSION__: JSON.stringify(pkg.version), + // Pass SENTRY_DSN to renderer as VITE_SENTRY_DSN (Vite replaces at compile time) + 'import.meta.env.VITE_SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN ?? ''), + }, resolve: { alias: { '@renderer': resolve(__dirname, 'src/renderer'), @@ -101,8 +130,9 @@ export default defineConfig({ '@main': resolve(__dirname, 'src/main') } }, - plugins: [react()], + plugins: [react(), ...sentryPlugins], build: { + sourcemap: 'hidden', rollupOptions: { input: { index: resolve(__dirname, 'src/renderer/index.html') diff --git a/package.json b/package.json index 1519519b..1344b645 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,8 @@ "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@sentry/electron": "^7.10.0", + "@sentry/react": "^10.45.0", "@tanstack/react-virtual": "^3.10.8", "@tiptap/extension-placeholder": "^3.20.1", "@tiptap/markdown": "^3.20.1", @@ -162,6 +164,7 @@ "@electron/rebuild": "^4.0.3", "@eslint-community/eslint-plugin-eslint-comments": "^4.6.0", "@eslint/js": "^9.39.2", + "@sentry/vite-plugin": "^5.1.1", "@tailwindcss/typography": "^0.5.19", "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", @@ -212,7 +215,8 @@ "files": [ "out/renderer/**", "dist-electron/**", - "package.json" + "package.json", + "!**/*.map" ], "asar": true, "asarUnpack": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2d63940..ff2756a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,6 +140,12 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@sentry/electron': + specifier: ^7.10.0 + version: 7.10.0 + '@sentry/react': + specifier: ^10.45.0 + version: 10.45.0(react@18.3.1) '@tanstack/react-virtual': specifier: ^3.10.8 version: 3.13.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -294,6 +300,9 @@ importers: '@eslint/js': specifier: ^9.39.2 version: 9.39.2 + '@sentry/vite-plugin': + specifier: ^5.1.1 + version: 5.1.1(encoding@0.1.13)(rollup@4.55.1) '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) @@ -1114,6 +1123,11 @@ packages: '@fastify/merge-json-schemas@0.2.1': resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + '@fastify/otel@0.16.0': + resolution: {integrity: sha512-2304BdM5Q/kUvQC9qJO1KZq3Zn1WWsw+WWkVmFEaj1UE2hEIiuFqrPeglQOwEtw/ftngisqfQ3v70TWMmwhhHA==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} @@ -1332,6 +1346,216 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs + '@opentelemetry/api-logs@0.207.0': + resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.211.0': + resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.6.0': + resolution: {integrity: sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.5.0': + resolution: {integrity: sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.6.0': + resolution: {integrity: sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/instrumentation-amqplib@0.58.0': + resolution: {integrity: sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.54.0': + resolution: {integrity: sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dataloader@0.28.0': + resolution: {integrity: sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.59.0': + resolution: {integrity: sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.30.0': + resolution: {integrity: sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.54.0': + resolution: {integrity: sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.58.0': + resolution: {integrity: sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.57.0': + resolution: {integrity: sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.211.0': + resolution: {integrity: sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.59.0': + resolution: {integrity: sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.20.0': + resolution: {integrity: sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.55.0': + resolution: {integrity: sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.59.0': + resolution: {integrity: sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.55.0': + resolution: {integrity: sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.64.0': + resolution: {integrity: sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.57.0': + resolution: {integrity: sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.57.0': + resolution: {integrity: sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.57.0': + resolution: {integrity: sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.63.0': + resolution: {integrity: sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis@0.59.0': + resolution: {integrity: sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.30.0': + resolution: {integrity: sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.21.0': + resolution: {integrity: sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation@0.207.0': + resolution: {integrity: sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.208.0': + resolution: {integrity: sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.211.0': + resolution: {integrity: sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/redis-common@0.38.2': + resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@opentelemetry/resources@2.6.0': + resolution: {integrity: sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.6.0': + resolution: {integrity: sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@oxc-resolver/binding-android-arm-eabi@11.16.4': resolution: {integrity: sha512-6XUHilmj8D6Ggus+sTBp64x/DUQ7LgC/dvTDdUOt4iMQnDdSep6N1mnvVLIiG+qM5tRnNHravNzBJnUlYwRQoA==} cpu: [arm] @@ -1439,6 +1663,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@prisma/instrumentation@7.2.0': + resolution: {integrity: sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==} + peerDependencies: + '@opentelemetry/api': ^1.8 + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -2019,6 +2248,179 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sentry-internal/browser-utils@10.42.0': + resolution: {integrity: sha512-HCEICKvepxN4/6NYfnMMMlppcSwIEwtS66X6d1/mwaHdi2ivw0uGl52p7Nfhda/lIJArbrkWprxl0WcjZajhQA==} + engines: {node: '>=18'} + + '@sentry-internal/browser-utils@10.45.0': + resolution: {integrity: sha512-ZPZpeIarXKScvquGx2AfNKcYiVNDA4wegMmjyGVsTA2JPmP0TrJoO3UybJS6KGDeee8V3I3EfD/ruauMm7jOFQ==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.42.0': + resolution: {integrity: sha512-lpPcHsog10MVYFTWE0Pf8vQRqQWwZHJpkVl2FEb9/HDdHFyTBUhCVoWo1KyKaG7GJl9AVKMAg7bp9SSNArhFNQ==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.45.0': + resolution: {integrity: sha512-vCSurazFVq7RUeYiM5X326jA5gOVrWYD6lYX2fbjBOMcyCEhDnveNxMT62zKkZDyNT/jyD194nz/cjntBUkyWA==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.42.0': + resolution: {integrity: sha512-am3m1Fj8ihoPfoYo41Qq4KeCAAICn4bySso8Oepu9dMNe9Lcnsf+reMRS2qxTPg3pZDc4JEMOcLyNCcgnAfrHw==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.45.0': + resolution: {integrity: sha512-nvq/AocdZTuD7y0KSiWi3gVaY0s5HOFy86mC/v1kDZmT/jsBAzN5LDkk/f1FvsWma1peqQmpUqxvhC+YIW294Q==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.42.0': + resolution: {integrity: sha512-Zh3EoaH39x2lqVY1YyVB2vJEyCIrT+YLUQxYl1yvP0MJgLxaR6akVjkgxbSUJahan4cX5DxpZiEHfzdlWnYPyQ==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.45.0': + resolution: {integrity: sha512-vjosRoGA1bzhVAEO1oce+CsRdd70quzBeo7WvYqpcUnoLe/Rv8qpOMqWX3j26z7XfFHMExWQNQeLxmtYOArvlw==} + engines: {node: '>=18'} + + '@sentry/babel-plugin-component-annotate@5.1.1': + resolution: {integrity: sha512-x2wEpBHwsTyTF2rWsLKJlzrRF1TTIGOfX+ngdE+Yd5DBkoS58HwQv824QOviPGQRla4/ypISqAXzjdDPL/zalg==} + engines: {node: '>= 18'} + + '@sentry/browser@10.42.0': + resolution: {integrity: sha512-iXxYjXNEBwY1MH4lDSDZZUNjzPJDK7/YLwVIJq/3iBYpIQVIhaJsoJnf3clx9+NfJ8QFKyKfcvgae61zm+hgTA==} + engines: {node: '>=18'} + + '@sentry/browser@10.45.0': + resolution: {integrity: sha512-e/a8UMiQhqqv706McSIcG6XK+AoQf9INthi2pD+giZfNRTzXTdqHzUT5OIO5hg8Am6eF63nDJc+vrYNPhzs51Q==} + engines: {node: '>=18'} + + '@sentry/bundler-plugin-core@5.1.1': + resolution: {integrity: sha512-F+itpwR9DyQR7gEkrXd2tigREPTvtF5lC8qu6e4anxXYRTui1+dVR0fXNwjpyAZMhIesLfXRN7WY7ggdj7hi0Q==} + engines: {node: '>= 18'} + + '@sentry/cli-darwin@2.58.5': + resolution: {integrity: sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ==} + engines: {node: '>=10'} + os: [darwin] + + '@sentry/cli-linux-arm64@2.58.5': + resolution: {integrity: sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux, freebsd, android] + + '@sentry/cli-linux-arm@2.58.5': + resolution: {integrity: sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux, freebsd, android] + + '@sentry/cli-linux-i686@2.58.5': + resolution: {integrity: sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [linux, freebsd, android] + + '@sentry/cli-linux-x64@2.58.5': + resolution: {integrity: sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux, freebsd, android] + + '@sentry/cli-win32-arm64@2.58.5': + resolution: {integrity: sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@sentry/cli-win32-i686@2.58.5': + resolution: {integrity: sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [win32] + + '@sentry/cli-win32-x64@2.58.5': + resolution: {integrity: sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@sentry/cli@2.58.5': + resolution: {integrity: sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg==} + engines: {node: '>= 10'} + hasBin: true + + '@sentry/core@10.42.0': + resolution: {integrity: sha512-L4rMrXMqUKBanpjpMT+TuAVk6xAijz6AWM6RiEYpohAr7SGcCEc1/T0+Ep1eLV8+pwWacfU27OvELIyNeOnGzA==} + engines: {node: '>=18'} + + '@sentry/core@10.45.0': + resolution: {integrity: sha512-s69UXxvefeQxuZ5nY7/THtTrIEvJxNVCp3ns4kwoCw1qMpgpvn/296WCKVmM7MiwnaAdzEKnAvLAwaxZc2nM7Q==} + engines: {node: '>=18'} + + '@sentry/electron@7.10.0': + resolution: {integrity: sha512-RwifPIBQds31giWL5KF87R/owzcVamMcXkL2ctoe/ybsxV861cbNhXxba/XCI6YmYOGIaixqiCAasxeZ+mx1SA==} + peerDependencies: + '@sentry/node-native': 10.42.0 + peerDependenciesMeta: + '@sentry/node-native': + optional: true + + '@sentry/node-core@10.42.0': + resolution: {integrity: sha512-9tf3fPV6M071aps72D+PEtdQPTuj+SuqO2+PpTfdPP5ZL4TTKYo3VK0li76SL+5wGdTFGV5qmsokHq9IRBA0iA==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/resources': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/context-async-hooks': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/resources': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + '@opentelemetry/semantic-conventions': + optional: true + + '@sentry/node@10.42.0': + resolution: {integrity: sha512-ZZfU3Fnni7Aj0lTX4e3QpY3UxK4FGuzfM20316UAJycBGnripm+sDHwcekPMGfLnk/FrN9wa1atspVlHvOI0WQ==} + engines: {node: '>=18'} + + '@sentry/opentelemetry@10.42.0': + resolution: {integrity: sha512-5vsYz683iihzlIj3sT1+tEixf0awwXK86a+aYsnMHrTXJDrkBDq4U0ZT+yxdPfJlkaxRtYycFR08SXr2pSm7Eg==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.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 + + '@sentry/react@10.45.0': + resolution: {integrity: sha512-jLezuxi4BUIU3raKyAPR5xMbQG/nhwnWmKo5p11NCbLmWzkS+lxoyDTUB4B8TAKZLfdtdkKLOn1S0tFc8vbUHw==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + + '@sentry/rollup-plugin@5.1.1': + resolution: {integrity: sha512-1d5NkdRR6aKWBP7czkY8sFFWiKnfmfRpQOj+m9bJTsyTjbMiEQJst6315w5pCVlRItPhBqpAraqAhutZFgvyVg==} + engines: {node: '>= 18'} + peerDependencies: + rollup: '>=3.2.0' + + '@sentry/vite-plugin@5.1.1': + resolution: {integrity: sha512-i6NWUDi2SDikfSUeMJvJTRdwEKYSfTd+mvBO2Ja51S1YK+hnickBuDfD+RvPerIXLuyRu3GamgNPbNqgCGUg/Q==} + engines: {node: '>= 18'} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -2240,6 +2642,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -2381,6 +2786,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} @@ -2396,6 +2804,12 @@ packages: '@types/node@25.0.7': resolution: {integrity: sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==} + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -2416,6 +2830,9 @@ packages: '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -2669,6 +3086,11 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3110,6 +3532,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -4118,6 +4543,9 @@ packages: engines: {node: '>=18.3.0'} hasBin: true + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -4250,6 +4678,10 @@ packages: resolution: {integrity: sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==} engines: {node: 20 || >=22} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -4463,6 +4895,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -5270,6 +5705,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -5286,6 +5725,9 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5348,6 +5790,15 @@ packages: resolution: {integrity: sha512-vLh2xJFSyniBLYDEDbXKqD32fQ5vAxmYT4hco8t0EHQ4CQ4BDHhshi7kdvDc6Y1MwGSi1Mhl4unUukPbCayZdw==} engines: {bun: '>=1.3.0', node: '>=18'} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-gyp@11.5.0: resolution: {integrity: sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -5538,6 +5989,10 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -5555,6 +6010,17 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -5666,6 +6132,22 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6016,6 +6498,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resedit@1.7.2: resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} engines: {node: '>=12', npm: '>=6'} @@ -6564,6 +7050,9 @@ packages: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -6916,10 +7405,16 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -7003,6 +7498,10 @@ packages: zod-to-json-schema: optional: true + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -7922,6 +8421,16 @@ snapshots: dependencies: dequal: 2.0.3 + '@fastify/otel@0.16.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + minimatch: 10.2.2 + transitivePeerDependencies: + - supports-color + '@fastify/proxy-addr@5.1.0': dependencies: '@fastify/forwarded': 3.0.1 @@ -8230,6 +8739,274 @@ snapshots: mkdirp: 1.0.4 rimraf: 3.0.2 + '@opentelemetry/api-logs@0.207.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.208.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.211.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/instrumentation-amqplib@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.28.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.20.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.64.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.63.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.7 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.21.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.207.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/redis-common@0.38.2': {} + + '@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/semantic-conventions@1.40.0': {} + + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@oxc-resolver/binding-android-arm-eabi@11.16.4': optional: true @@ -8297,6 +9074,13 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@prisma/instrumentation@7.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -8834,6 +9618,216 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@sentry-internal/browser-utils@10.42.0': + dependencies: + '@sentry/core': 10.42.0 + + '@sentry-internal/browser-utils@10.45.0': + dependencies: + '@sentry/core': 10.45.0 + + '@sentry-internal/feedback@10.42.0': + dependencies: + '@sentry/core': 10.42.0 + + '@sentry-internal/feedback@10.45.0': + dependencies: + '@sentry/core': 10.45.0 + + '@sentry-internal/replay-canvas@10.42.0': + dependencies: + '@sentry-internal/replay': 10.42.0 + '@sentry/core': 10.42.0 + + '@sentry-internal/replay-canvas@10.45.0': + dependencies: + '@sentry-internal/replay': 10.45.0 + '@sentry/core': 10.45.0 + + '@sentry-internal/replay@10.42.0': + dependencies: + '@sentry-internal/browser-utils': 10.42.0 + '@sentry/core': 10.42.0 + + '@sentry-internal/replay@10.45.0': + dependencies: + '@sentry-internal/browser-utils': 10.45.0 + '@sentry/core': 10.45.0 + + '@sentry/babel-plugin-component-annotate@5.1.1': {} + + '@sentry/browser@10.42.0': + dependencies: + '@sentry-internal/browser-utils': 10.42.0 + '@sentry-internal/feedback': 10.42.0 + '@sentry-internal/replay': 10.42.0 + '@sentry-internal/replay-canvas': 10.42.0 + '@sentry/core': 10.42.0 + + '@sentry/browser@10.45.0': + dependencies: + '@sentry-internal/browser-utils': 10.45.0 + '@sentry-internal/feedback': 10.45.0 + '@sentry-internal/replay': 10.45.0 + '@sentry-internal/replay-canvas': 10.45.0 + '@sentry/core': 10.45.0 + + '@sentry/bundler-plugin-core@5.1.1(encoding@0.1.13)': + dependencies: + '@babel/core': 7.28.6 + '@sentry/babel-plugin-component-annotate': 5.1.1 + '@sentry/cli': 2.58.5(encoding@0.1.13) + dotenv: 16.6.1 + find-up: 5.0.0 + glob: 13.0.6 + magic-string: 0.30.21 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/cli-darwin@2.58.5': + optional: true + + '@sentry/cli-linux-arm64@2.58.5': + optional: true + + '@sentry/cli-linux-arm@2.58.5': + optional: true + + '@sentry/cli-linux-i686@2.58.5': + optional: true + + '@sentry/cli-linux-x64@2.58.5': + optional: true + + '@sentry/cli-win32-arm64@2.58.5': + optional: true + + '@sentry/cli-win32-i686@2.58.5': + optional: true + + '@sentry/cli-win32-x64@2.58.5': + optional: true + + '@sentry/cli@2.58.5(encoding@0.1.13)': + dependencies: + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0(encoding@0.1.13) + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + optionalDependencies: + '@sentry/cli-darwin': 2.58.5 + '@sentry/cli-linux-arm': 2.58.5 + '@sentry/cli-linux-arm64': 2.58.5 + '@sentry/cli-linux-i686': 2.58.5 + '@sentry/cli-linux-x64': 2.58.5 + '@sentry/cli-win32-arm64': 2.58.5 + '@sentry/cli-win32-i686': 2.58.5 + '@sentry/cli-win32-x64': 2.58.5 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/core@10.42.0': {} + + '@sentry/core@10.45.0': {} + + '@sentry/electron@7.10.0': + dependencies: + '@sentry/browser': 10.42.0 + '@sentry/core': 10.42.0 + '@sentry/node': 10.42.0 + transitivePeerDependencies: + - supports-color + + '@sentry/node-core@10.42.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)': + dependencies: + '@sentry/core': 10.42.0 + '@sentry/opentelemetry': 10.42.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + import-in-the-middle: 2.0.6 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@sentry/node@10.42.0': + dependencies: + '@fastify/otel': 0.16.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.20.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.64.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.63.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.21.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@prisma/instrumentation': 7.2.0(@opentelemetry/api@1.9.0) + '@sentry/core': 10.42.0 + '@sentry/node-core': 10.42.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + '@sentry/opentelemetry': 10.42.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + import-in-the-middle: 2.0.6 + transitivePeerDependencies: + - supports-color + + '@sentry/opentelemetry@10.42.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + '@sentry/core': 10.42.0 + + '@sentry/react@10.45.0(react@18.3.1)': + dependencies: + '@sentry/browser': 10.45.0 + '@sentry/core': 10.45.0 + react: 18.3.1 + + '@sentry/rollup-plugin@5.1.1(encoding@0.1.13)(rollup@4.55.1)': + dependencies: + '@sentry/bundler-plugin-core': 5.1.1(encoding@0.1.13) + magic-string: 0.30.21 + rollup: 4.55.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/vite-plugin@5.1.1(encoding@0.1.13)(rollup@4.55.1)': + dependencies: + '@sentry/bundler-plugin-core': 5.1.1(encoding@0.1.13) + '@sentry/rollup-plugin': 5.1.1(encoding@0.1.13)(rollup@4.55.1) + transitivePeerDependencies: + - encoding + - rollup + - supports-color + '@sindresorhus/is@4.6.0': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -9093,6 +10087,10 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.0.7 + '@types/d3-array@3.2.2': {} '@types/d3-axis@3.0.6': @@ -9257,6 +10255,10 @@ snapshots: '@types/ms@2.1.0': {} + '@types/mysql@2.15.27': + dependencies: + '@types/node': 25.0.7 + '@types/node@18.19.130': dependencies: undici-types: 5.26.5 @@ -9277,6 +10279,16 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/pg-pool@2.0.7': + dependencies: + '@types/pg': 8.15.6 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 25.0.7 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + '@types/plist@3.0.5': dependencies: '@types/node': 25.0.7 @@ -9302,6 +10314,10 @@ snapshots: dependencies: '@types/node': 18.19.130 + '@types/tedious@4.0.14': + dependencies: + '@types/node': 25.0.7 + '@types/trusted-types@2.0.7': optional: true @@ -9570,6 +10586,10 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -10192,6 +11212,8 @@ snapshots: ci-info@3.9.0: {} + cjs-module-lexer@2.2.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -11516,6 +12538,8 @@ snapshots: dependencies: fd-package-json: 2.0.0 + forwarded-parse@2.1.2: {} + forwarded@0.2.0: {} fraction.js@5.3.4: {} @@ -11662,6 +12686,12 @@ snapshots: minipass: 7.1.2 path-scurry: 2.0.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.2 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -11957,6 +12987,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@2.0.6: + dependencies: + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -13007,6 +14044,8 @@ snapshots: minipass@7.1.2: {} + minipass@7.1.3: {} + minizlib@2.1.2: dependencies: minipass: 3.3.6 @@ -13025,6 +14064,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 + module-details-from-path@1.0.4: {} + ms@2.1.3: {} mz@2.7.0: @@ -13071,6 +14112,12 @@ snapshots: node-diff3@3.2.0: {} + node-fetch@2.7.0(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + node-gyp@11.5.0: dependencies: env-paths: 2.2.1 @@ -13311,6 +14358,11 @@ snapshots: lru-cache: 11.2.6 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.6 + minipass: 7.1.3 + path-to-regexp@8.3.0: {} pathe@2.0.3: {} @@ -13321,6 +14373,18 @@ snapshots: pend@1.2.0: {} + pg-int8@1.0.1: {} + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -13442,6 +14506,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prelude-ls@1.2.1: {} prettier-plugin-tailwindcss@0.7.2(prettier@3.8.1): @@ -13831,6 +14905,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + resedit@1.7.2: dependencies: pe-library: 0.4.1 @@ -14502,6 +15583,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + tr46@0.0.3: {} + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -14961,8 +16044,15 @@ snapshots: web-namespaces@2.0.1: {} + webidl-conversions@3.0.1: {} + whatwg-mimetype@3.0.0: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -15052,6 +16142,8 @@ snapshots: zod: 4.3.6 zod-to-json-schema: 3.25.1(zod@4.3.6) + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/src/main/index.ts b/src/main/index.ts index 02bb817c..eb2ed895 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,6 +16,10 @@ // On Windows this saturates all threads, blocking the event loop. process.env.UV_THREADPOOL_SIZE ??= '16'; +// Sentry must be the first import to capture early errors. +import './sentry'; +import { syncTelemetryFlag } from './sentry'; + import { JsonScheduleRepository } from '@main/services/schedule/JsonScheduleRepository'; import { ScheduledTaskExecutor } from '@main/services/schedule/ScheduledTaskExecutor'; import { SchedulerService } from '@main/services/schedule/SchedulerService'; @@ -1320,6 +1324,9 @@ void app.whenReady().then(() => { // Apply configuration settings const config = configManager.getConfig(); + // Sync Sentry telemetry opt-in flag from persisted config + syncTelemetryFlag(config.general.telemetryEnabled); + // Apply launch-at-login setting only in packaged builds. // In dev, macOS may deny this (and Electron logs a noisy error to stderr). // Also guard by platform: Electron only supports this on macOS/Windows. diff --git a/src/main/ipc/config.ts b/src/main/ipc/config.ts index 838ad650..9bf6c1e8 100644 --- a/src/main/ipc/config.ts +++ b/src/main/ipc/config.ts @@ -18,6 +18,7 @@ */ import { getAutoDetectedClaudeBasePath, getClaudeBasePath } from '@main/utils/pathDecoder'; +import { syncTelemetryFlag } from '@main/sentry'; import { getErrorMessage } from '@shared/utils/errorHandling'; import { createLogger } from '@shared/utils/logger'; import { execFile } from 'child_process'; @@ -171,6 +172,14 @@ async function handleUpdateConfig( configManager.updateConfig(validation.section, validation.data); + // Sync Sentry opt-in when general.telemetryEnabled changes + if ( + validation.section === 'general' && + Object.prototype.hasOwnProperty.call(validation.data, 'telemetryEnabled') + ) { + syncTelemetryFlag(configManager.getConfig().general.telemetryEnabled); + } + if (isClaudeRootUpdate && onClaudeRootPathUpdated) { const nextClaudeRootPath = (validation.data as { claudeRootPath?: string | null }) .claudeRootPath; diff --git a/src/main/ipc/configValidation.ts b/src/main/ipc/configValidation.ts index 28571967..9d322564 100644 --- a/src/main/ipc/configValidation.ts +++ b/src/main/ipc/configValidation.ts @@ -290,6 +290,7 @@ function validateGeneralSection(data: unknown): ValidationSuccess<'general'> | V 'agentLanguage', 'autoExpandAIGroups', 'useNativeTitleBar', + 'telemetryEnabled', ]; const result: Partial = {}; @@ -372,6 +373,12 @@ function validateGeneralSection(data: unknown): ValidationSuccess<'general'> | V } result.useNativeTitleBar = value; break; + case 'telemetryEnabled': + if (typeof value !== 'boolean') { + return { valid: false, error: `general.${key} must be a boolean` }; + } + result.telemetryEnabled = value; + break; default: return { valid: false, error: `Unsupported general key: ${key}` }; } diff --git a/src/main/ipc/ipcWrapper.ts b/src/main/ipc/ipcWrapper.ts index 4d8d6583..792b27a2 100644 --- a/src/main/ipc/ipcWrapper.ts +++ b/src/main/ipc/ipcWrapper.ts @@ -5,6 +5,7 @@ * and returns IpcResult for consistent renderer-side handling. */ +import { addMainBreadcrumb } from '@main/sentry'; import { createLogger } from '@shared/utils/logger'; import type { IpcResult } from '@shared/types/ipc'; @@ -13,6 +14,7 @@ export function createIpcWrapper(logPrefix: string) { const log = createLogger(logPrefix); return async function wrap(operation: string, fn: () => Promise): Promise> { + addMainBreadcrumb('ipc', `${logPrefix}:${operation}`); try { const data = await fn(); return { success: true, data }; diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 34ff37ae..7eba1c6b 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -1,4 +1,5 @@ import { setCurrentMainOp } from '@main/services/infrastructure/EventLoopLagMonitor'; +import { addMainBreadcrumb } from '@main/sentry'; import { getAppIconPath } from '@main/utils/appIcon'; import { getAppDataPath, getTeamsBasePath } from '@main/utils/pathDecoder'; import { stripMarkdown } from '@main/utils/textFormatting'; @@ -876,16 +877,17 @@ async function handleCreateTeam( return { success: false, error: validation.error }; } - return wrapTeamHandler('create', () => - getTeamProvisioningService().createTeam(validation.value, (progress) => { + return wrapTeamHandler('create', () => { + addMainBreadcrumb('team', 'create', { teamName: validation.value.teamName }); + return getTeamProvisioningService().createTeam(validation.value, (progress) => { try { event.sender.send(TEAM_PROVISIONING_PROGRESS, progress); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.warn(`Failed to emit provisioning progress: ${message}`); } - }) - ); + }); + }); } async function handleLaunchTeam( @@ -979,8 +981,9 @@ async function handleLaunchTeam( ); } - return wrapTeamHandler('launch', () => - getTeamProvisioningService().launchTeam( + return wrapTeamHandler('launch', () => { + addMainBreadcrumb('team', 'launch', { teamName: validatedTeamName.value! }); + return getTeamProvisioningService().launchTeam( { teamName: validatedTeamName.value!, cwd, @@ -1005,8 +1008,8 @@ async function handleLaunchTeam( logger.warn(`Failed to emit launch provisioning progress: ${message}`); } } - ) - ); + ); + }); } async function handleValidateCliArgs( @@ -2055,6 +2058,7 @@ async function handleStopTeam( return { success: false, error: validated.error ?? 'Invalid teamName' }; } return wrapTeamHandler('stop', async () => { + addMainBreadcrumb('team', 'stop', { teamName: validated.value! }); getTeamProvisioningService().stopTeam(validated.value!); }); } diff --git a/src/main/sentry.ts b/src/main/sentry.ts new file mode 100644 index 00000000..7c0fb2b7 --- /dev/null +++ b/src/main/sentry.ts @@ -0,0 +1,79 @@ +/** + * Sentry initialisation for the Electron **main** process. + * + * Must be imported at the very top of `src/main/index.ts` (and `standalone.ts`) + * so that Sentry captures errors from the earliest point possible. + * + * When `SENTRY_DSN` is not set (dev / self-builds), everything is a no-op. + */ + +import * as Sentry from '@sentry/electron/main'; +import { + isValidDsn, + SENTRY_ENVIRONMENT, + SENTRY_RELEASE, + TRACES_SAMPLE_RATE, +} from '@shared/utils/sentryConfig'; + +// --------------------------------------------------------------------------- +// Telemetry gate +// --------------------------------------------------------------------------- + +// Module-level flag that `beforeSend` checks. +// Updated by `syncTelemetryFlag()` once ConfigManager is ready. +// Defaults to `true` so early crash reports are NOT silently dropped; +// if the user later turns telemetry off, the flag flips to `false`. +let telemetryAllowed = true; + +/** + * Call once ConfigManager is initialised to sync the opt-in flag. + * Also call whenever the config changes (e.g. user toggles telemetry in Settings). + */ +export function syncTelemetryFlag(enabled: boolean): void { + telemetryAllowed = enabled; +} + +// --------------------------------------------------------------------------- +// Init +// --------------------------------------------------------------------------- + +const dsn = process.env.SENTRY_DSN; +let initialized = false; + +if (isValidDsn(dsn)) { + Sentry.init({ + dsn, + release: SENTRY_RELEASE, + environment: SENTRY_ENVIRONMENT, + tracesSampleRate: TRACES_SAMPLE_RATE, + sendDefaultPii: false, + + beforeSend(event) { + return telemetryAllowed ? event : null; + }, + }); + initialized = true; +} + +// --------------------------------------------------------------------------- +// Public helpers (no-op when Sentry is not configured) +// --------------------------------------------------------------------------- + +/** Record a breadcrumb visible in subsequent error events. */ +export function addMainBreadcrumb( + category: string, + message: string, + data?: Record +): void { + if (!initialized) return; + Sentry.addBreadcrumb({ category, message, data, level: 'info' }); +} + +/** + * Wrap a synchronous or async function in a Sentry performance span. + * Returns the function's return value transparently. + */ +export function startMainSpan(name: string, op: string, fn: () => T): T { + if (!initialized) return fn(); + return Sentry.startSpan({ name, op }, fn); +} diff --git a/src/main/services/analysis/ChunkBuilder.ts b/src/main/services/analysis/ChunkBuilder.ts index bf02f3aa..617eef96 100644 --- a/src/main/services/analysis/ChunkBuilder.ts +++ b/src/main/services/analysis/ChunkBuilder.ts @@ -39,6 +39,7 @@ import { import { calculateMetrics } from '@main/utils/jsonl'; import { createLogger } from '@shared/utils/logger'; +import { startMainSpan } from '../../sentry'; import type { WaterfallData, WaterfallItem } from '@shared/types'; const logger = createLogger('Service:ChunkBuilder'); @@ -80,81 +81,83 @@ export class ChunkBuilder { subagents: Process[] = [], options?: { includeSidechain?: boolean } ): EnhancedChunk[] { - const chunks: EnhancedChunk[] = []; + return startMainSpan('chunks.build', 'build', () => { + const chunks: EnhancedChunk[] = []; - // Filter to main thread messages (non-sidechain) - const mainMessages = options?.includeSidechain - ? messages - : messages.filter((m) => !m.isSidechain); - logger.debug(`Total messages: ${messages.length}, Main thread: ${mainMessages.length}`); + // Filter to main thread messages (non-sidechain) + const mainMessages = options?.includeSidechain + ? messages + : messages.filter((m) => !m.isSidechain); + logger.debug(`Total messages: ${messages.length}, Main thread: ${mainMessages.length}`); - // Classify each message into categories using MessageClassifier - const classified = classifyMessages(mainMessages); + // Classify each message into categories using MessageClassifier + const classified = classifyMessages(mainMessages); - // Log classification summary - const categoryCounts = new Map(); - for (const { category } of classified) { - categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1); - } - logger.debug('Message classification:', Object.fromEntries(categoryCounts)); - - // Build chunks from classification - AI chunks are INDEPENDENT - let aiBuffer: ParsedMessage[] = []; - - for (const { message, category } of classified) { - switch (category) { - case 'hardNoise': - // Skip - filtered out - break; - - case 'compact': - // Flush any buffered AI messages first - if (aiBuffer.length > 0) { - chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); - aiBuffer = []; - } - chunks.push(buildCompactChunk(message)); - break; - - case 'user': - // Flush any buffered AI messages first - if (aiBuffer.length > 0) { - chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); - aiBuffer = []; - } - chunks.push(buildUserChunk(message)); - break; - - case 'system': - // Flush any buffered AI messages first - if (aiBuffer.length > 0) { - chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); - aiBuffer = []; - } - chunks.push(buildSystemChunk(message)); - break; - - case 'ai': - aiBuffer.push(message); - break; + // Log classification summary + const categoryCounts = new Map(); + for (const { category } of classified) { + categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1); } - } + logger.debug('Message classification:', Object.fromEntries(categoryCounts)); - // Flush remaining AI buffer - if (aiBuffer.length > 0) { - chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); - } + // Build chunks from classification - AI chunks are INDEPENDENT + let aiBuffer: ParsedMessage[] = []; - // Log final chunk summary - const userChunkCount = chunks.filter(isUserChunk).length; - const aiChunkCount = chunks.filter(isAIChunk).length; - const systemChunkCount = chunks.filter(isSystemChunk).length; - const compactChunkCount = chunks.filter(isCompactChunk).length; - logger.debug( - `Created ${chunks.length} chunks: ${userChunkCount} user, ${aiChunkCount} AI, ${systemChunkCount} system, ${compactChunkCount} compact` - ); + for (const { message, category } of classified) { + switch (category) { + case 'hardNoise': + // Skip - filtered out + break; - return chunks; + case 'compact': + // Flush any buffered AI messages first + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + aiBuffer = []; + } + chunks.push(buildCompactChunk(message)); + break; + + case 'user': + // Flush any buffered AI messages first + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + aiBuffer = []; + } + chunks.push(buildUserChunk(message)); + break; + + case 'system': + // Flush any buffered AI messages first + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + aiBuffer = []; + } + chunks.push(buildSystemChunk(message)); + break; + + case 'ai': + aiBuffer.push(message); + break; + } + } + + // Flush remaining AI buffer + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + } + + // Log final chunk summary + const userChunkCount = chunks.filter(isUserChunk).length; + const aiChunkCount = chunks.filter(isAIChunk).length; + const systemChunkCount = chunks.filter(isSystemChunk).length; + const compactChunkCount = chunks.filter(isCompactChunk).length; + logger.debug( + `Created ${chunks.length} chunks: ${userChunkCount} user, ${aiChunkCount} AI, ${systemChunkCount} system, ${compactChunkCount} compact` + ); + + return chunks; + }); // startMainSpan } // =========================================================================== diff --git a/src/main/services/discovery/SessionSearcher.ts b/src/main/services/discovery/SessionSearcher.ts index a4382c98..67bf5d1c 100644 --- a/src/main/services/discovery/SessionSearcher.ts +++ b/src/main/services/discovery/SessionSearcher.ts @@ -15,6 +15,8 @@ import { LocalFileSystemProvider } from '@main/services/infrastructure/LocalFile import { parseJsonlFile } from '@main/utils/jsonl'; import { extractBaseDir, extractSessionId } from '@main/utils/pathDecoder'; import { createLogger } from '@shared/utils/logger'; + +import { startMainSpan } from '../../sentry'; import { extractMarkdownPlainText, findMarkdownSearchMatches, @@ -62,135 +64,140 @@ export class SessionSearcher { query: string, maxResults: number = 50 ): Promise { - const startedAt = Date.now(); - const results: SearchResult[] = []; - let sessionsSearched = 0; - const fastMode = this.fsProvider.type === 'ssh'; - let isPartial = false; + return startMainSpan('session.search', 'search', async () => { + const startedAt = Date.now(); + const results: SearchResult[] = []; + let sessionsSearched = 0; + const fastMode = this.fsProvider.type === 'ssh'; + let isPartial = false; - if (!query || query.trim().length === 0) { - return { results: [], totalMatches: 0, sessionsSearched: 0, query }; - } - - const normalizedQuery = query.toLowerCase().trim(); - - try { - const baseDir = extractBaseDir(projectId); - const projectPath = path.join(this.projectsDir, baseDir); - const sessionFilter = subprojectRegistry.getSessionFilter(projectId); - - if (!(await this.fsProvider.exists(projectPath))) { + if (!query || query.trim().length === 0) { return { results: [], totalMatches: 0, sessionsSearched: 0, query }; } - // Get all session files - const entries = await this.fsProvider.readdir(projectPath); - const sessionEntries = entries.filter((entry) => { - if (!entry.isFile() || !entry.name.endsWith('.jsonl')) return false; - // Filter to only sessions belonging to this subproject - if (sessionFilter) { - const sessionId = extractSessionId(entry.name); - return sessionFilter.has(sessionId); - } - return true; - }); - const sessionFiles = await this.collectFulfilledInBatches( - sessionEntries, - this.fsProvider.type === 'ssh' ? 24 : 128, - async (entry) => { - const filePath = path.join(projectPath, entry.name); - const mtimeMs = - typeof entry.mtimeMs === 'number' - ? entry.mtimeMs - : (await this.fsProvider.stat(filePath)).mtimeMs; - return { name: entry.name, filePath, mtimeMs }; - } - ); - sessionFiles.sort((a, b) => b.mtimeMs - a.mtimeMs); + const normalizedQuery = query.toLowerCase().trim(); - // Search session files with bounded concurrency and staged breadth in SSH mode. - const searchBatchSize = fastMode ? 3 : 8; - const stageBoundaries = fastMode - ? this.buildFastSearchStageBoundaries(sessionFiles.length) - : [sessionFiles.length]; - let searchedUntil = 0; - let shouldStop = false; + try { + const baseDir = extractBaseDir(projectId); + const projectPath = path.join(this.projectsDir, baseDir); + const sessionFilter = subprojectRegistry.getSessionFilter(projectId); - for (const stageBoundary of stageBoundaries) { - for ( - let i = searchedUntil; - i < stageBoundary && results.length < maxResults; - i += searchBatchSize - ) { - if (fastMode && Date.now() - startedAt >= SSH_FAST_SEARCH_TIME_BUDGET_MS) { - isPartial = true; - shouldStop = true; + if (!(await this.fsProvider.exists(projectPath))) { + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; + } + + // Get all session files + const entries = await this.fsProvider.readdir(projectPath); + const sessionEntries = entries.filter((entry) => { + if (!entry.isFile() || !entry.name.endsWith('.jsonl')) return false; + // Filter to only sessions belonging to this subproject + if (sessionFilter) { + const sessionId = extractSessionId(entry.name); + return sessionFilter.has(sessionId); + } + return true; + }); + const sessionFiles = await this.collectFulfilledInBatches( + sessionEntries, + this.fsProvider.type === 'ssh' ? 24 : 128, + async (entry) => { + const filePath = path.join(projectPath, entry.name); + const mtimeMs = + typeof entry.mtimeMs === 'number' + ? entry.mtimeMs + : (await this.fsProvider.stat(filePath)).mtimeMs; + return { name: entry.name, filePath, mtimeMs }; + } + ); + sessionFiles.sort((a, b) => b.mtimeMs - a.mtimeMs); + + // Search session files with bounded concurrency and staged breadth in SSH mode. + const searchBatchSize = fastMode ? 3 : 8; + const stageBoundaries = fastMode + ? this.buildFastSearchStageBoundaries(sessionFiles.length) + : [sessionFiles.length]; + let searchedUntil = 0; + let shouldStop = false; + + for (const stageBoundary of stageBoundaries) { + for ( + let i = searchedUntil; + i < stageBoundary && results.length < maxResults; + i += searchBatchSize + ) { + if (fastMode && Date.now() - startedAt >= SSH_FAST_SEARCH_TIME_BUDGET_MS) { + isPartial = true; + shouldStop = true; + break; + } + + const batch = sessionFiles.slice(i, i + searchBatchSize); + sessionsSearched += batch.length; + + const settled = await Promise.allSettled( + batch.map(async (file) => { + const sessionId = extractSessionId(file.name); + return this.searchSessionFile( + projectId, + sessionId, + file.filePath, + normalizedQuery, + maxResults, + file.mtimeMs + ); + }) + ); + + for (const result of settled) { + if (results.length >= maxResults) { + break; + } + if (result.status !== 'fulfilled' || result.value.length === 0) { + continue; + } + + const remaining = maxResults - results.length; + results.push(...result.value.slice(0, remaining)); + } + } + + searchedUntil = stageBoundary; + + if (shouldStop || !fastMode || results.length >= maxResults) { break; } - const batch = sessionFiles.slice(i, i + searchBatchSize); - sessionsSearched += batch.length; - - const settled = await Promise.allSettled( - batch.map(async (file) => { - const sessionId = extractSessionId(file.name); - return this.searchSessionFile( - projectId, - sessionId, - file.filePath, - normalizedQuery, - maxResults, - file.mtimeMs - ); - }) - ); - - for (const result of settled) { - if (results.length >= maxResults) { - break; - } - if (result.status !== 'fulfilled' || result.value.length === 0) { - continue; - } - - const remaining = maxResults - results.length; - results.push(...result.value.slice(0, remaining)); + if ( + stageBoundary < sessionFiles.length && + results.length >= SSH_FAST_SEARCH_MIN_RESULTS + ) { + isPartial = true; + break; } } - searchedUntil = stageBoundary; - - if (shouldStop || !fastMode || results.length >= maxResults) { - break; - } - - if (stageBoundary < sessionFiles.length && results.length >= SSH_FAST_SEARCH_MIN_RESULTS) { + if (fastMode && results.length < maxResults && sessionsSearched < sessionFiles.length) { isPartial = true; - break; } - } - if (fastMode && results.length < maxResults && sessionsSearched < sessionFiles.length) { - isPartial = true; - } + if (fastMode) { + logger.debug( + `SSH fast search scanned ${sessionsSearched}/${sessionFiles.length} sessions in ${Date.now() - startedAt}ms (results=${results.length}, partial=${isPartial})` + ); + } - if (fastMode) { - logger.debug( - `SSH fast search scanned ${sessionsSearched}/${sessionFiles.length} sessions in ${Date.now() - startedAt}ms (results=${results.length}, partial=${isPartial})` - ); + return { + results, + totalMatches: results.length, + sessionsSearched, + query, + isPartial: fastMode ? isPartial : undefined, + }; + } catch (error) { + logger.error(`Error searching sessions for project ${projectId}:`, error); + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; } - - return { - results, - totalMatches: results.length, - sessionsSearched, - query, - isPartial: fastMode ? isPartial : undefined, - }; - } catch (error) { - logger.error(`Error searching sessions for project ${projectId}:`, error); - return { results: [], totalMatches: 0, sessionsSearched: 0, query }; - } + }); // startMainSpan } /** diff --git a/src/main/services/infrastructure/ConfigManager.ts b/src/main/services/infrastructure/ConfigManager.ts index 07894773..a1d09b79 100644 --- a/src/main/services/infrastructure/ConfigManager.ts +++ b/src/main/services/infrastructure/ConfigManager.ts @@ -210,6 +210,8 @@ export interface GeneralConfig { useNativeTitleBar: boolean; /** Paths manually added via "Select Folder" that persist across app restarts */ customProjectPaths: string[]; + /** Send anonymous crash & performance telemetry (requires SENTRY_DSN at build time) */ + telemetryEnabled: boolean; } export interface DisplayConfig { @@ -293,6 +295,7 @@ const DEFAULT_CONFIG: AppConfig = { autoExpandAIGroups: false, useNativeTitleBar: false, customProjectPaths: [], + telemetryEnabled: true, }, display: { showTimestamps: true, diff --git a/src/main/services/parsing/SessionParser.ts b/src/main/services/parsing/SessionParser.ts index b0fb3f0c..093dcaaf 100644 --- a/src/main/services/parsing/SessionParser.ts +++ b/src/main/services/parsing/SessionParser.ts @@ -24,6 +24,7 @@ import { } from '@main/utils/jsonl'; import * as path from 'path'; +import { startMainSpan } from '../../sentry'; import { type ProjectScanner } from '../discovery/ProjectScanner'; /** @@ -74,8 +75,10 @@ export class SessionParser { * Parse a JSONL file at the given path. */ async parseSessionFile(filePath: string): Promise { - const messages = await parseJsonlFile(filePath, this.projectScanner.getFileSystemProvider()); - return this.processMessages(messages); + return startMainSpan('session.parse', 'parse', async () => { + const messages = await parseJsonlFile(filePath, this.projectScanner.getFileSystemProvider()); + return this.processMessages(messages); + }); } /** diff --git a/src/main/standalone.ts b/src/main/standalone.ts index 0ff2b18d..13d5ff45 100644 --- a/src/main/standalone.ts +++ b/src/main/standalone.ts @@ -12,6 +12,10 @@ * - CORS_ORIGIN: CORS origin policy (default '*') */ +// Note: Sentry is NOT imported here. @sentry/electron/main requires Electron +// runtime which is unavailable in standalone (pure Node.js) mode. Standalone +// error tracking can be added later with @sentry/node if needed. + import { createLogger } from '@shared/utils/logger'; import { HttpServer } from './services/infrastructure/HttpServer'; diff --git a/src/renderer/components/common/ErrorBoundary.tsx b/src/renderer/components/common/ErrorBoundary.tsx index 5e975989..9c4b69cc 100644 --- a/src/renderer/components/common/ErrorBoundary.tsx +++ b/src/renderer/components/common/ErrorBoundary.tsx @@ -1,5 +1,6 @@ import React, { Component, type ErrorInfo, type ReactNode } from 'react'; +import { captureRendererException, isSentryRendererActive } from '@renderer/sentry'; import { useStore } from '@renderer/store'; import { type BugReportContext, @@ -43,6 +44,14 @@ export class ErrorBoundary extends Component { componentDidCatch(error: Error, errorInfo: ErrorInfo): void { logger.error('ErrorBoundary caught an error:', error, errorInfo); this.setState({ errorInfo }); + + // Report to Sentry when telemetry is active + if (isSentryRendererActive()) { + captureRendererException(error, { + componentStack: errorInfo.componentStack, + ...this.getBugReportContext(), + }); + } } handleReload = (): void => { diff --git a/src/renderer/components/settings/hooks/useSettingsConfig.ts b/src/renderer/components/settings/hooks/useSettingsConfig.ts index 9d9585b5..bc6b84f8 100644 --- a/src/renderer/components/settings/hooks/useSettingsConfig.ts +++ b/src/renderer/components/settings/hooks/useSettingsConfig.ts @@ -33,6 +33,7 @@ export interface SafeConfig { agentLanguage: string; autoExpandAIGroups: boolean; useNativeTitleBar: boolean; + telemetryEnabled: boolean; }; notifications: { enabled: boolean; @@ -172,6 +173,7 @@ export function useSettingsConfig(): UseSettingsConfigReturn { agentLanguage: displayConfig?.general?.agentLanguage ?? 'system', autoExpandAIGroups: displayConfig?.general?.autoExpandAIGroups ?? false, useNativeTitleBar: displayConfig?.general?.useNativeTitleBar ?? false, + telemetryEnabled: displayConfig?.general?.telemetryEnabled ?? true, }, notifications: { enabled: displayConfig?.notifications?.enabled ?? true, diff --git a/src/renderer/components/settings/hooks/useSettingsHandlers.ts b/src/renderer/components/settings/hooks/useSettingsHandlers.ts index 50de430d..76af24e2 100644 --- a/src/renderer/components/settings/hooks/useSettingsHandlers.ts +++ b/src/renderer/components/settings/hooks/useSettingsHandlers.ts @@ -318,6 +318,7 @@ export function useSettingsHandlers({ agentLanguage: 'system', autoExpandAIGroups: false, useNativeTitleBar: false, + telemetryEnabled: true, }, display: { showTimestamps: true, diff --git a/src/renderer/components/settings/sections/GeneralSection.tsx b/src/renderer/components/settings/sections/GeneralSection.tsx index 7f2fdbbf..dd0ebf11 100644 --- a/src/renderer/components/settings/sections/GeneralSection.tsx +++ b/src/renderer/components/settings/sections/GeneralSection.tsx @@ -674,6 +674,23 @@ export const GeneralSection = ({

)} + + {/* Privacy / Telemetry — only visible when Sentry DSN is baked into the build */} + {import.meta.env.VITE_SENTRY_DSN && ( + <> + + + onGeneralToggle('telemetryEnabled', v)} + disabled={saving} + /> + + + )} ); }; diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index 0422a83c..74e4b2c4 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -6,6 +6,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { App } from './App'; +import { initSentryRenderer } from './sentry'; import { initializeNotificationListeners } from './store'; declare global { @@ -14,6 +15,9 @@ declare global { } } +// Sentry must be initialised before React renders. +initSentryRenderer(); + // React 18 StrictMode intentionally mounts/unmounts effects twice in dev, // which can start duplicate IPC init chains. Make initialization a one-time // module-level side effect guarded by a global flag. diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts new file mode 100644 index 00000000..06d81cfc --- /dev/null +++ b/src/renderer/sentry.ts @@ -0,0 +1,110 @@ +/** + * Sentry initialisation for the **renderer** process. + * + * Must be called before `ReactDOM.createRoot()` in `main.tsx`. + * Supports both Electron (preload bridge) and standalone browser mode. + * + * When `VITE_SENTRY_DSN` is not set (dev / self-builds), everything is a no-op. + */ + +import * as SentryElectron from '@sentry/electron/renderer'; +import { browserTracingIntegration as reactBrowserTracing, init as reactInit } from '@sentry/react'; +import { + isValidDsn, + SENTRY_ENVIRONMENT, + SENTRY_RELEASE, + TRACES_SAMPLE_RATE, +} from '@shared/utils/sentryConfig'; + +// --------------------------------------------------------------------------- +// Telemetry gate (mirrors src/main/sentry.ts pattern) +// --------------------------------------------------------------------------- + +// Defaults to `true` so early renderer crashes are captured. +// Synced to user's telemetryEnabled preference via syncRendererTelemetry(). +let telemetryAllowed = true; +let initialized = false; + +/** + * Sync the opt-in flag from config. Call after config is loaded + * and whenever the user toggles telemetry in Settings. + */ +export function syncRendererTelemetry(enabled: boolean): void { + telemetryAllowed = enabled; +} + +export function initSentryRenderer(): void { + if (initialized) return; + + const dsn = import.meta.env.VITE_SENTRY_DSN as string | undefined; + if (!isValidDsn(dsn)) return; + + const baseOptions = { + dsn, + release: SENTRY_RELEASE, + environment: SENTRY_ENVIRONMENT, + tracesSampleRate: TRACES_SAMPLE_RATE, + sendDefaultPii: false, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- cross-version @sentry/core type mismatch + const beforeSend = (event: any) => (telemetryAllowed ? event : null); + + if (window.electronAPI) { + // Electron renderer — uses IPC transport to main process. + // browserTracingIntegration from @sentry/electron/renderer to avoid + // @sentry/core version mismatch with @sentry/react. + SentryElectron.init({ + ...baseOptions, + beforeSend, + integrations: [SentryElectron.browserTracingIntegration()], + }); + } else { + // Standalone browser mode — direct HTTP transport + reactInit({ + ...baseOptions, + beforeSend, + integrations: [reactBrowserTracing()], + }); + } + + initialized = true; +} + +/** Whether the renderer SDK was successfully initialised. */ +export function isSentryRendererActive(): boolean { + return initialized; +} + +// --------------------------------------------------------------------------- +// Public helpers (no-op when Sentry is not configured) +// --------------------------------------------------------------------------- + +/** Record a navigation breadcrumb (tab switches). */ +export function addNavigationBreadcrumb(from: string, to: string): void { + if (!initialized) return; + SentryElectron.addBreadcrumb({ + category: 'navigation', + message: `Tab: ${from} → ${to}`, + level: 'info', + }); +} + +/** Record a generic breadcrumb from the renderer. */ +export function addRendererBreadcrumb( + category: string, + message: string, + data?: Record +): void { + if (!initialized) return; + SentryElectron.addBreadcrumb({ category, message, data, level: 'info' }); +} + +/** Capture an exception with optional extra context. */ +export function captureRendererException(error: Error, context?: Record): void { + if (!initialized) return; + SentryElectron.withScope((scope) => { + if (context) scope.setContext('react', context); + SentryElectron.captureException(error); + }); +} diff --git a/src/renderer/store/index.ts b/src/renderer/store/index.ts index 019223a8..509534b9 100644 --- a/src/renderer/store/index.ts +++ b/src/renderer/store/index.ts @@ -3,6 +3,7 @@ */ import { api } from '@renderer/api'; +import { syncRendererTelemetry } from '@renderer/sentry'; import { cleanupStale as cleanupCommentReadState } from '@renderer/services/commentReadStorage'; import { create } from 'zustand'; @@ -96,6 +97,10 @@ export function initializeNotificationListeners(): () => void { // Config: fast (in-memory read) — needed for theme before first paint. await useStore.getState().fetchConfig(); + // Sync Sentry renderer telemetry gate from loaded config + const loadedConfig = useStore.getState().appConfig; + syncRendererTelemetry(loadedConfig?.general?.telemetryEnabled ?? true); + // Remaining fetches have no data dependency on each other — run in parallel // to avoid blocking teams/notifications behind a slow repository scan. await Promise.all([ diff --git a/src/renderer/store/slices/configSlice.ts b/src/renderer/store/slices/configSlice.ts index 66122ad5..94c9a920 100644 --- a/src/renderer/store/slices/configSlice.ts +++ b/src/renderer/store/slices/configSlice.ts @@ -3,6 +3,7 @@ */ import { api } from '@renderer/api'; +import { syncRendererTelemetry } from '@renderer/sentry'; import { createLogger } from '@shared/utils/logger'; import type { AppState } from '../types'; @@ -66,6 +67,8 @@ export const createConfigSlice: StateCreator = (s // Refresh config after update const config = await api.config.get(); set({ appConfig: config }); + // Sync Sentry telemetry gate when config changes + syncRendererTelemetry(config.general?.telemetryEnabled ?? true); } catch (error) { logger.error('Failed to update config:', error); set({ diff --git a/src/renderer/store/slices/tabSlice.ts b/src/renderer/store/slices/tabSlice.ts index a755bf9a..acacf8ab 100644 --- a/src/renderer/store/slices/tabSlice.ts +++ b/src/renderer/store/slices/tabSlice.ts @@ -13,6 +13,7 @@ import { truncateLabel, } from '@renderer/types/tabs'; import { normalizePath } from '@renderer/utils/pathNormalize'; +import { addNavigationBreadcrumb } from '@renderer/sentry'; import { findPane, @@ -254,6 +255,14 @@ export const createTabSlice: StateCreator = (set, ge const state = get(); const { paneLayout } = state; + // Sentry breadcrumb for tab navigation + const prevTab = state.getActiveTab(); + const targetPane = findPaneByTabId(paneLayout, tabId); + const targetTab = targetPane?.tabs.find((t) => t.id === tabId); + if (prevTab?.id !== tabId) { + addNavigationBreadcrumb(prevTab?.label ?? 'none', targetTab?.label ?? tabId); + } + // Find which pane contains this tab const pane = findPaneByTabId(paneLayout, tabId); if (!pane) return; diff --git a/src/shared/types/notifications.ts b/src/shared/types/notifications.ts index 5e963300..cd36ca7b 100644 --- a/src/shared/types/notifications.ts +++ b/src/shared/types/notifications.ts @@ -316,6 +316,8 @@ export interface AppConfig { autoExpandAIGroups: boolean; /** Whether to use the native OS title bar instead of the custom one (Linux/Windows) */ useNativeTitleBar: boolean; + /** Send anonymous crash & performance telemetry (requires SENTRY_DSN at build time) */ + telemetryEnabled: boolean; }; /** Display and UI settings */ display: { diff --git a/src/shared/utils/sentryConfig.ts b/src/shared/utils/sentryConfig.ts new file mode 100644 index 00000000..186a8f25 --- /dev/null +++ b/src/shared/utils/sentryConfig.ts @@ -0,0 +1,25 @@ +/** + * Shared Sentry configuration constants. + * + * Used by both main and renderer process init modules. + * Does NOT resolve DSN — each process does that with its own env access + * (main: process.env, renderer: import.meta.env). + */ + +declare const __APP_VERSION__: string; + +/** Release identifier injected at build time via Vite `define`. */ +export const SENTRY_RELEASE = + typeof __APP_VERSION__ === 'string' ? `claude-agent-teams-ui@${__APP_VERSION__}` : undefined; + +/** Environment derived from Node/Vite mode. */ +export const SENTRY_ENVIRONMENT = + process.env.NODE_ENV === 'production' ? 'production' : 'development'; + +/** Performance trace sample rate (production: 10%, dev: 100%). */ +export const TRACES_SAMPLE_RATE = process.env.NODE_ENV === 'production' ? 0.1 : 1.0; + +/** Validate that a string looks like a Sentry DSN. */ +export function isValidDsn(dsn: string | undefined): dsn is string { + return typeof dsn === 'string' && dsn.length > 0 && dsn.startsWith('https://'); +} diff --git a/test/setup.ts b/test/setup.ts index 1cc3cc1b..7e49d0bd 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -5,6 +5,20 @@ import { afterEach, beforeEach, expect, vi } from 'vitest'; +// Mock Sentry Electron SDK — it requires the real `electron` package at import +// time which is unavailable in the vitest/happy-dom environment. +const sentryNoOp = { + init: vi.fn(), + addBreadcrumb: vi.fn(), + captureException: vi.fn(), + startSpan: vi.fn((_opts: unknown, fn: () => unknown) => fn()), + withScope: vi.fn((fn: (scope: unknown) => void) => fn({ setContext: vi.fn() })), + browserTracingIntegration: vi.fn(() => ({ name: 'BrowserTracing', setup: vi.fn(), afterAllSetup: vi.fn() })), +}; +vi.mock('@sentry/electron/main', () => sentryNoOp); +vi.mock('@sentry/electron/renderer', () => sentryNoOp); +vi.mock('@sentry/react', () => sentryNoOp); + // Mock HOME for tests that need a predictable home path. Use stubEnv so we never // touch process itself — stubbing process breaks vitest (process.listeners etc). vi.stubEnv('HOME', '/home/testuser');