diff --git a/PROMPTS.md b/PROMPTS.md index a073cde..7b751da 100644 --- a/PROMPTS.md +++ b/PROMPTS.md @@ -92,16 +92,19 @@ Directory: `apps/slack-agent`. Full lab: [labs/04-slack-agent.md](labs/04-slack- ```text Instrument this Node.js Slack bot with Sentry. Create a new Sentry project for it in my organization. I need: -- Agent tracing for the Vercel AI SDK call in `src/agent.ts`, with inputs -and outputs recorded. +- Agent tracing for the Vercel AI SDK call in `src/agent.ts`. - One transaction per Slack message handled in `src/app.ts`, so each -message shows as one trace with the model calls and tool calls inside it. +message shows as one trace with the model call and its tool calls inside it. - The Slack thread timestamp set as the conversation id, and the Slack user id set as the Sentry user. -- Database spans for the Postgres queries under `../storefront/lib/db`. -- Full trace sampling for the workshop. -Use the 11.0 release candidate of the Sentry SDK, npm tag `next`. Add only -what these points need. +- Prompts and replies recorded only when the message is in a public or +private channel, never in a direct message. +- One span per warehouse query in `src/analytics/tools.ts`, tagged with the +metric name, the date range length, and the row count, so a direct message +trace still shows something useful without the prompt and the reply. +- The release read from `SENTRY_RELEASE`, and full trace sampling for the +workshop. +Use the 11.0 release of the Sentry SDK. Add only what these points need. The process starts with `npm run dev`. When done, start it, wait for the "connected over Socket Mode" line, and tell me how to confirm the first trace arrived. diff --git a/README.md b/README.md index 7d6831a..69e4f08 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ each lab names the one that did the work. | App | Environment | Runtime | You do | | --- | --- | --- | --- | | `apps/storefront` | Browser chat on a Next.js app | Next.js, Vercel AI SDK, Postgres on Neon | Hands-on | -| `apps/slack-agent` | Long-lived Slack bot | Node, Bolt (Socket Mode), Vercel AI SDK, same Postgres | Follow along | +| `apps/slack-agent` | Long-lived Slack bot: product analytics assistant over a Postgres warehouse (Neon) | Node, Bolt (Socket Mode), Vercel AI SDK | Follow along | | `apps/pr-reviewer` | GitHub Actions job | Node, Flue (OpenTelemetry), OpenRouter | Follow along | All three call Claude Sonnet 5 through OpenRouter: the storefront and the @@ -42,7 +42,7 @@ Do these once, before the day. Each takes a few minutes. See workshop, so no account is needed. 3. A coding agent with Sentry's MCP server attached. Claude Code users install the `sentry` plugin. Other agents add . -4. Node 22 and git. +4. Node 22.18 or newer, and git. Then: diff --git a/RUNSHEET.md b/RUNSHEET.md index a166ff3..e135128 100644 --- a/RUNSHEET.md +++ b/RUNSHEET.md @@ -78,26 +78,39 @@ applied with `scripts/solution.sh `. Time on the day: 55 minutes. three levels up. Tool errors the model recovers from still deserve a record, because silent recovery is how agents hide failures. -## Lab 4. The same agent in Slack (10 minutes, follow-along) +## Lab 4. A different agent in Slack (10 minutes, follow-along) - **Outcome.** One trace per Slack message: a root span with the thread timestamp as conversation id and the Slack user as Sentry user, the model - call and tool spans inside. After the regression patch, cached input - tokens on later turns drop from thousands to zero. -- **Prompt.** The lab 4 prompt (no session replay, no logs; the trace is all - you get), then "Find the latest trace in the slack-agent project ...", - then "Compare cached input tokens per model call between the last two - releases". -- **Code.** `src/instrument.ts`: six lines, imported first. `src/app.ts`: the + call and its `analytics.scan` tool spans inside, each with a `db` child + span for the Postgres query. A channel message records the prompt and the + reply; a direct message never does, but its scan spans still carry the + metric, the range length, and the row count. After the regression patch, + cached input tokens on later turns drop from thousands to zero. +- **Prompt.** The lab 4 instrument prompt (one transaction per message, + content recorded only outside direct messages, one span per warehouse + scan), then "Find the latest trace in the slack-agent project ...", then + the same question asked in a channel and in a DM, then "Compare cached + input tokens per model call between the last two releases". +- **Code.** `src/instrument.ts`: Sentry init plus the `dataCollection` block + that turns content recording off by default. `src/app.ts`: the `Sentry.startSpan` wrapper around one message with - `gen_ai.conversation.id` and `setUser`. Then the regression: + `gen_ai.conversation.id`, `setUser`, and the `slack.record_content` + attribute. `src/agent.ts`: the per-call `telemetry` override that follows + the same channel-vs-DM rule. `src/analytics/tools.ts`: one + `Sentry.startSpan` per warehouse scan. Then the regression: `regressions/drop-prompt-cache.patch` removes one line, the `providerOptions` cache breakpoint on the system prompt. -- **Sentry UI.** The trace. Explore > Spans, query +- **Sentry UI.** The channel trace next to the DM trace for the same + question: one shows the prompt, the reply, and the tool arguments; the + other shows only the scan spans' attributes and their `db` child spans. + Then Explore > Spans, query `gen_ai.usage.cache_read.input_tokens` grouped by release: one release shows 3114, the next shows 0. - **Takeaway.** The prompt names the unit of work. Conversation id and user - come from the platform. A cache regression is one attribute per release. + come from the platform. Content recording can follow a rule the platform + already gives you, such as channel type. A cache regression is one + attribute per release. ## Lab 5. The same agent in GitHub Actions (10 minutes, follow-along) diff --git a/apps/slack-agent/.env.example b/apps/slack-agent/.env.example index 9e69a32..6ff18d6 100644 --- a/apps/slack-agent/.env.example +++ b/apps/slack-agent/.env.example @@ -8,9 +8,17 @@ SLACK_APP_TOKEN= # OpenRouter API key — shared key from the presenter OPENROUTER_API_KEY= -# Copy from apps/storefront/.env.local - the bot reads the same database. +# Same Neon connection string as apps/storefront/.env.local DATABASE_URL= -# Optional. One of the SUPPORTED_MODELS in ../storefront/lib/ai/models.ts. -# Defaults to anthropic/claude-sonnet-5. +# Optional. One of anthropic/claude-sonnet-5, anthropic/claude-haiku-4.5, +# openai/gpt-5-mini, google/gemini-2.5-flash. Defaults to +# anthropic/claude-sonnet-5. # OPENROUTER_MODEL=anthropic/claude-sonnet-5 + +# Sentry DSN for this project (Project Settings > Client Keys) +SENTRY_DSN= +# Optional. Defaults to "development". +SENTRY_ENVIRONMENT= +# Optional. Tags every span and error with the release that produced it. +SENTRY_RELEASE= diff --git a/apps/slack-agent/README.md b/apps/slack-agent/README.md index d1eb232..0735c69 100644 --- a/apps/slack-agent/README.md +++ b/apps/slack-agent/README.md @@ -1,46 +1,96 @@ # slack-agent -A Slack bot that answers shopping questions in a DM or when mentioned. It -reuses the storefront's tools (`searchProducts`, `getProduct`, -`getAccountInfo`, `refundOrder`) and the same Postgres database, so it can -search the catalog and manage the same demo customer's orders. It reads -the whole thread on every message, so follow-ups like "refund that order" -work. +A Slack bot that answers product analytics questions in a DM or when +mentioned. It reads from `analytics_daily`, a Postgres table on Neon with +ninety days of history, seeded from a deterministic generator in +`src/analytics/generate.ts`. It reads the whole thread on every message, so +follow-ups like "and by device?" work. + +## What it can answer + +Four tools back every answer: + +- `list_metrics` - the metric catalog: `signups`, `active_users`, + `page_views`, `checkout_conversion`, `error_rate`, each with its unit and + the dimensions it can be grouped by. +- `query_metric` - one metric over a date range, with an optional + `groupBy` for a per-dimension breakdown. +- `compare_periods` - one metric's total in a current range against a + previous range, with the absolute and percent change. +- `top_movers` - which values of a dimension moved the most between the + first and second half of a date range. + +The system prompt tells the assistant to call a tool before stating any +number, name the metric and the exact date range it used, and compare +against the previous period of the same length when asked how something +"did". + +## Data collection + +The assistant records its prompts and replies only when the message came +from a public or private channel - never from a direct message. A DM still +produces a trace: the root `slack.message` span and, for every warehouse +query, an `analytics.scan` span with the metric name, the range length, and +the row count, with a `db` span inside it for the Postgres query, none of +which need the prompt or the reply to be useful. ## Setup -1. From the repo root, run `scripts/setup.sh` first. It installs the - storefront, provisions a Postgres database, and seeds it. -2. `npm run manifest:link` and open the printed URL in a Slack Developer +1. `npm run manifest:link` and open the printed URL in a Slack Developer Program sandbox to create the app from this app's manifest. -3. Install the app to your workspace. -4. On the app's Basic Information page, create an app-level token with the +2. Install the app to your workspace. +3. On the app's Basic Information page, create an app-level token with the `connections:write` scope. -5. Copy `.env.example` to `.env.local`. Fill in `SLACK_BOT_TOKEN` (from OAuth - & Permissions) and `SLACK_APP_TOKEN` (the token from step 4), plus - `OPENROUTER_API_KEY` and the `DATABASE_URL` from - `apps/storefront/.env.local`. +4. Copy `.env.example` to `.env.local`. Fill in `SLACK_BOT_TOKEN` (from OAuth + & Permissions), `SLACK_APP_TOKEN` (the token from step 3), and + `OPENROUTER_API_KEY`. +5. Paste the storefront's `DATABASE_URL` (from `apps/storefront/.env.local`) + into `.env.local`. 6. `npm install` -7. `npm run dev` -8. DM the bot: "show me the apparel collection". +7. `npm run db:seed`. The warehouse ends on the day you seed, so run this + again on the workshop day. +8. `npm run dev` +9. DM the bot: "How did signups do last week compared with the week before?" ## What the agent does in Slack - It shows a status message while it works. - It streams the reply as it writes it. - It shows each tool call as a task in a timeline. -- It adds a card for each product that the reply names. The button on a card - asks the agent about that product in the same thread. -- It adds a table row for each order that the reply names. -- It adds a warning line when a tool call fails, for example a refund. +- It adds a warning line when a tool call fails, for example a query outside + the 90-day retention window. - It names each thread after the first message. - It stops the model call when the user presses Slack's stop button. - It adds feedback buttons under each reply. - In a DM, the Messages tab shows four suggested prompts from the manifest. +Every reply ends with a chart card and a table built from that turn's tool +results. QuickChart renders the chart from a URL. Slack fetches the image. +The bot makes no request. A URL over 3000 characters drops the chart, so +long ranges are bucketed by week. + Note: the manifest uses Slack's agent messaging experience (`agent_view`). An app created from an older manifest must have its manifest replaced on the App Manifest page at api.slack.com, then be reinstalled. Slack does not let an app go back to the older assistant experience. -Sentry is added to this app in `labs/04-slack-agent.md`. +## Sentry + +Set `SENTRY_DSN` in `.env.local` (`SENTRY_ENVIRONMENT` and `SENTRY_RELEASE` +are optional). Each handled Slack message becomes one trace: a root +`slack.message` span tagged with the Slack channel, the thread timestamp as +the conversation id, and whether content recording is on for that channel, +with the model call and its tool calls nested inside as `gen_ai` spans and +one `analytics.scan` span per warehouse query, each with a `db` child span +from the `pg` integration. The dev script starts Node with +`--import ./src/instrument.ts`, so Sentry loads before `pg`. A runner that +compiles TypeScript itself, such as tsx, skips that hook and shows no db +spans. The Sentry user is the Slack user who sent the message. Errors go to +Sentry as issues. + +## Regression + +`regressions/drop-prompt-cache.patch` removes the cache breakpoint from the +system prompt, so Anthropic caches nothing and cached input tokens fall to +zero on every turn. Apply from the repository root with +`git apply apps/slack-agent/regressions/drop-prompt-cache.patch`. diff --git a/apps/slack-agent/package-lock.json b/apps/slack-agent/package-lock.json index d4fa987..1c6c73b 100644 --- a/apps/slack-agent/package-lock.json +++ b/apps/slack-agent/package-lock.json @@ -7,21 +7,20 @@ "name": "slack-agent", "dependencies": { "@openrouter/ai-sdk-provider": "3.0.0", + "@sentry/node": "11.0.0", "@slack/bolt": "^4.4.0", "ai": "7.0.64", "dotenv": "17.4.2", - "drizzle-orm": "0.45.2", - "pg": "8.23.0", + "pg": "^8.16.3", "zod": "4.4.3" }, "devDependencies": { "@types/node": "22.20.1", - "@types/pg": "8.23.1", - "tsx": "4.23.13", + "@types/pg": "^8.15.5", "typescript": "7.0.2" }, "engines": { - "node": ">=22" + "node": ">=22.18" } }, "node_modules/@ai-sdk/gateway": { @@ -41,7 +40,19 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { + "node_modules/@ai-sdk/provider": { + "version": "4.0.7", + "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider/-/provider-4.0.7.tgz", + "integrity": "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@ai-sdk/provider-utils": { "version": "5.0.27", "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider-utils/-/provider-utils-5.0.27.tgz", "integrity": "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw==", @@ -60,471 +71,745 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/provider": { - "version": "4.0.7", - "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider/-/provider-4.0.7.tgz", - "integrity": "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==", - "license": "Apache-2.0", + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", "dependencies": { - "json-schema": "^0.4.0" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=22" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", - "optional": true, - "os": [ - "aix" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://sfw.security.sentry.io/npm/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://sfw.security.sentry.io/npm/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://sfw.security.sentry.io/npm/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://sfw.security.sentry.io/npm/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://sfw.security.sentry.io/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://sfw.security.sentry.io/npm/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://sfw.security.sentry.io/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://sfw.security.sentry.io/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://sfw.security.sentry.io/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@openrouter/ai-sdk-provider": { + "version": "3.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@openrouter/ai-sdk-provider/-/ai-sdk-provider-3.0.0.tgz", + "integrity": "sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==", + "license": "Apache-2.0", + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "ai": "^7.0.0", + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://sfw.security.sentry.io/npm/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.143.0.tgz", + "integrity": "sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==", "cpu": [ - "arm64" + "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.143.0.tgz", + "integrity": "sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.143.0.tgz", + "integrity": "sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.143.0.tgz", + "integrity": "sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.143.0.tgz", + "integrity": "sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.143.0.tgz", + "integrity": "sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==", "cpu": [ - "x64" + "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.143.0.tgz", + "integrity": "sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.143.0.tgz", + "integrity": "sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==", "cpu": [ "arm64" ], - "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.143.0.tgz", + "integrity": "sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==", "cpu": [ - "ia32" + "arm64" + ], + "libc": [ + "musl" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.143.0.tgz", + "integrity": "sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==", "cpu": [ - "loong64" + "ppc64" + ], + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.143.0.tgz", + "integrity": "sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==", "cpu": [ - "mips64el" + "riscv64" + ], + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.143.0.tgz", + "integrity": "sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==", "cpu": [ - "ppc64" + "riscv64" + ], + "libc": [ + "musl" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.143.0.tgz", + "integrity": "sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==", "cpu": [ - "riscv64" + "s390x" + ], + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.143.0.tgz", + "integrity": "sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==", "cpu": [ - "s390x" + "x64" + ], + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.143.0.tgz", + "integrity": "sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==", "cpu": [ "x64" ], - "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.143.0.tgz", + "integrity": "sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "openharmony" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.143.0.tgz", + "integrity": "sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.143.0.tgz", + "integrity": "sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==", "cpu": [ - "arm64" + "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.143.0.tgz", + "integrity": "sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@sentry/bundler-plugins": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/bundler-plugins/-/bundler-plugins-11.0.0.tgz", + "integrity": "sha512-Oe4/8CbsYrqdjTt91JFMBQza59IWUfSwPrlHGbbJT+qVCyebX2bykxv3mfa6Ty/Gmt8+gva/DI6Kjcyb9EgXBg==", "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@babel/core": "^7.18.5", + "@sentry/core": "11.0.0", + "dotenv": "^17.4.2", + "glob": "^13.0.6", + "magic-string": "~0.30.8", + "oxc-parser": "^0.143.0", + "sentry": "^0.44.0", + "supports-color": "^8.1.1" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" + }, + "peerDependencies": { + "rollup": ">=3.2.0", + "webpack": ">=5.1.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@sentry/conventions": { + "version": "0.23.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/conventions/-/conventions-0.23.0.tgz", + "integrity": "sha512-+Euyo7CNVecoOusgUnq27oV97oshifgceKfnGnuKfXrPUVWA/cisvii3E1rlvy2yBrKvl9K1gCEo1Jqog5zjEQ==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=14" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@sentry/core": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/core/-/core-11.0.0.tgz", + "integrity": "sha512-Sdmg21POKmNwZDz8pH/2FtilWeryh3yxhRxbfA1ph8SnX2DkXoqX6G+uXyjCSYagW2pefVklmSNeWHmUlSqioA==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@sentry/conventions": "^0.23.0" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@sentry/node": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/node/-/node-11.0.0.tgz", + "integrity": "sha512-ZHk2wYvnKPfaq05YHnva/a6EzZDLTG0EStcWnQdjSae/OxluwCfOfMqwlsKxaY8KDUkyqokTCUPqJITZpUH1Jw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/bundler-plugins": "11.0.0", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0", + "@sentry/opentelemetry": "11.0.0", + "@sentry/server-runtime-injection": "11.0.0", + "@sentry/server-utils": "11.0.0" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@openrouter/ai-sdk-provider": { - "version": "3.0.0", - "resolved": "https://sfw.security.sentry.io/npm/@openrouter/ai-sdk-provider/-/ai-sdk-provider-3.0.0.tgz", - "integrity": "sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==", - "license": "Apache-2.0", + "node_modules/@sentry/opentelemetry": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/opentelemetry/-/opentelemetry-11.0.0.tgz", + "integrity": "sha512-m2EtgppTtN6MxW0dKA45aZMI/xoFZYi2I2cvTIenFGTGLfxyjk6+YCbg0Jf/wdUWoZjR0k6kBBBIbv+zpuvTOA==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0" + }, "engines": { - "node": ">=22" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "peerDependencies": { - "ai": "^7.0.0", - "zod": "^3.25.76 || ^4.1.8" + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@sentry/server-runtime-injection": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/server-runtime-injection/-/server-runtime-injection-11.0.0.tgz", + "integrity": "sha512-AE39xdGbi8WmcB9LtITuvJkp+sYxkPAR+3lRG4MYrJLVQS+YeYfn/AaZdEXOhnOagdpozsDbpEneGJn6vD8p6Q==", + "license": "MIT", + "dependencies": { + "@sentry/core": "11.0.0", + "@sentry/server-utils": "11.0.0" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" + } + }, + "node_modules/@sentry/server-utils": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/server-utils/-/server-utils-11.0.0.tgz", + "integrity": "sha512-XlKDtLoNqOhI+TgCNywan/tFy0yMRzw5+8mdp96KNHtF/YwO9s6c7qZcBG0x8+XDyC0EoNXDlBPJOMEZo5lD4Q==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, "node_modules/@slack/bolt": { @@ -722,7 +1007,7 @@ "version": "8.23.1", "resolved": "https://sfw.security.sentry.io/npm/@types/pg/-/pg-8.23.1.tgz", "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -1177,25 +1462,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/ai/node_modules/@ai-sdk/provider-utils": { - "version": "5.0.27", - "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider-utils/-/provider-utils-5.0.27.tgz", - "integrity": "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "4.0.7", - "@standard-schema/spec": "^1.1.0", - "@workflow/serde": "4.1.0", - "eventsource-parser": "^3.0.8", - "undici": "^7.28.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://sfw.security.sentry.io/npm/asynckit/-/asynckit-0.4.0.tgz", @@ -1214,6 +1480,27 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://sfw.security.sentry.io/npm/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.25", + "resolved": "https://sfw.security.sentry.io/npm/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz", + "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://sfw.security.sentry.io/npm/body-parser/-/body-parser-2.3.0.tgz", @@ -1230,25 +1517,70 @@ "raw-body": "^3.0.2", "type-is": "^2.1.0" }, - "engines": { - "node": ">=18" + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://sfw.security.sentry.io/npm/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://sfw.security.sentry.io/npm/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://sfw.security.sentry.io/npm/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://sfw.security.sentry.io/npm/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/buffer-equal-constant-time": { @@ -1295,6 +1627,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://sfw.security.sentry.io/npm/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://sfw.security.sentry.io/npm/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1329,6 +1681,12 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://sfw.security.sentry.io/npm/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://sfw.security.sentry.io/npm/cookie/-/cookie-0.7.2.tgz", @@ -1394,131 +1752,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "resolved": "https://sfw.security.sentry.io/npm/drizzle-orm/-/drizzle-orm-0.45.2.tgz", - "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://sfw.security.sentry.io/npm/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1548,6 +1781,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.438", + "resolved": "https://sfw.security.sentry.io/npm/electron-to-chromium/-/electron-to-chromium-1.5.438.tgz", + "integrity": "sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==", + "license": "ISC" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://sfw.security.sentry.io/npm/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1602,46 +1841,13 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://sfw.security.sentry.io/npm/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", - "dev": true, - "hasInstallScript": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://sfw.security.sentry.io/npm/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" + "node": ">=6" } }, "node_modules/escape-html": { @@ -1813,21 +2019,6 @@ "node": ">= 0.8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://sfw.security.sentry.io/npm/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://sfw.security.sentry.io/npm/function-bind/-/function-bind-1.1.2.tgz", @@ -1837,6 +2028,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://sfw.security.sentry.io/npm/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://sfw.security.sentry.io/npm/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1874,6 +2074,23 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://sfw.security.sentry.io/npm/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://sfw.security.sentry.io/npm/gopd/-/gopd-1.2.0.tgz", @@ -1886,6 +2103,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://sfw.security.sentry.io/npm/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://sfw.security.sentry.io/npm/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2013,12 +2239,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://sfw.security.sentry.io/npm/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://sfw.security.sentry.io/npm/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://sfw.security.sentry.io/npm/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "license": "(AFL-2.1 OR BSD-3-Clause)" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://sfw.security.sentry.io/npm/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://sfw.security.sentry.io/npm/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -2041,6 +2297,18 @@ "npm": ">=6" } }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://sfw.security.sentry.io/npm/jwa/-/jwa-2.0.1.tgz", @@ -2104,6 +2372,24 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://sfw.security.sentry.io/npm/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://sfw.security.sentry.io/npm/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://sfw.security.sentry.io/npm/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2163,6 +2449,30 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://sfw.security.sentry.io/npm/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://sfw.security.sentry.io/npm/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://sfw.security.sentry.io/npm/ms/-/ms-2.1.3.tgz", @@ -2198,6 +2508,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/node-releases": { + "version": "2.0.57", + "resolved": "https://sfw.security.sentry.io/npm/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://sfw.security.sentry.io/npm/object-inspect/-/object-inspect-1.13.4.tgz", @@ -2231,6 +2550,42 @@ "wrappy": "1" } }, + "node_modules/oxc-parser": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/oxc-parser/-/oxc-parser-0.143.0.tgz", + "integrity": "sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.143.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.143.0", + "@oxc-parser/binding-android-arm64": "0.143.0", + "@oxc-parser/binding-darwin-arm64": "0.143.0", + "@oxc-parser/binding-darwin-x64": "0.143.0", + "@oxc-parser/binding-freebsd-x64": "0.143.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.143.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.143.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.143.0", + "@oxc-parser/binding-linux-arm64-musl": "0.143.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.143.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.143.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.143.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.143.0", + "@oxc-parser/binding-linux-x64-gnu": "0.143.0", + "@oxc-parser/binding-linux-x64-musl": "0.143.0", + "@oxc-parser/binding-openharmony-arm64": "0.143.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.143.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.143.0", + "@oxc-parser/binding-win32-x64-msvc": "0.143.0" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://sfw.security.sentry.io/npm/p-finally/-/p-finally-1.0.0.tgz", @@ -2296,6 +2651,31 @@ "node": ">= 0.8" } }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://sfw.security.sentry.io/npm/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://sfw.security.sentry.io/npm/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://sfw.security.sentry.io/npm/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -2395,6 +2775,12 @@ "split2": "^4.1.0" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://sfw.security.sentry.io/npm/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://sfw.security.sentry.io/npm/postgres-array/-/postgres-array-2.0.0.tgz", @@ -2556,15 +2942,12 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "6.3.1", + "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -2593,6 +2976,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/sentry": { + "version": "0.44.1", + "resolved": "https://sfw.security.sentry.io/npm/sentry/-/sentry-0.44.1.tgz", + "integrity": "sha512-LK+RJ2PFsJKM+P0D/zYC+M5gFB1ytfVONU9DVUqJu67YFlwnj3wGJkUzBhCS8R4iUqRdRTAYbFbRKTAe03rW/Q==", + "license": "FSL-1.1-Apache-2.0", + "bin": { + "sentry": "dist/bin.cjs" + }, + "engines": { + "node": ">=20.0" + } + }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://sfw.security.sentry.io/npm/serve-static/-/serve-static-2.2.1.tgz", @@ -2708,6 +3103,21 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://sfw.security.sentry.io/npm/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://sfw.security.sentry.io/npm/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2726,25 +3136,6 @@ "node": ">=0.6.x" } }, - "node_modules/tsx": { - "version": "4.23.13", - "resolved": "https://sfw.security.sentry.io/npm/tsx/-/tsx-4.23.13.tgz", - "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://sfw.security.sentry.io/npm/type-is/-/type-is-2.1.0.tgz", @@ -2835,6 +3226,36 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://sfw.security.sentry.io/npm/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://sfw.security.sentry.io/npm/vary/-/vary-1.1.2.tgz", @@ -2880,6 +3301,12 @@ "node": ">=0.4" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://sfw.security.sentry.io/npm/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://sfw.security.sentry.io/npm/zod/-/zod-4.4.3.tgz", diff --git a/apps/slack-agent/package.json b/apps/slack-agent/package.json index d5c89ef..94f19e0 100644 --- a/apps/slack-agent/package.json +++ b/apps/slack-agent/package.json @@ -3,26 +3,27 @@ "private": true, "type": "module", "engines": { - "node": ">=22" + "node": ">=22.18" }, "scripts": { - "dev": "tsx --env-file=.env.local src/app.ts", + "dev": "node --env-file=.env.local --import ./src/instrument.ts src/app.ts", "typecheck": "tsc --noEmit", - "manifest:link": "node scripts/manifest-link.mjs" + "test": "node --test src/analytics/store.test.ts src/analytics/blocks.test.ts", + "manifest:link": "node scripts/manifest-link.mjs", + "db:seed": "node scripts/db-seed.ts" }, "dependencies": { "@openrouter/ai-sdk-provider": "3.0.0", + "@sentry/node": "11.0.0", "@slack/bolt": "^4.4.0", "ai": "7.0.64", "dotenv": "17.4.2", - "drizzle-orm": "0.45.2", - "pg": "8.23.0", + "pg": "^8.16.3", "zod": "4.4.3" }, "devDependencies": { "@types/node": "22.20.1", - "@types/pg": "8.23.1", - "tsx": "4.23.13", + "@types/pg": "^8.15.5", "typescript": "7.0.2" } } diff --git a/apps/slack-agent/regressions/drop-prompt-cache.patch b/apps/slack-agent/regressions/drop-prompt-cache.patch index e7b3076..299d457 100644 --- a/apps/slack-agent/regressions/drop-prompt-cache.patch +++ b/apps/slack-agent/regressions/drop-prompt-cache.patch @@ -1,20 +1,24 @@ diff --git a/apps/slack-agent/src/agent.ts b/apps/slack-agent/src/agent.ts -index cb03988..6ad9239 100644 +index b2f1a22..1dbe3cc 100644 --- a/apps/slack-agent/src/agent.ts +++ b/apps/slack-agent/src/agent.ts -@@ -1,7 +1,7 @@ - import { createOpenRouter } from "@openrouter/ai-sdk-provider"; - import { isStepCount, streamText } from "ai"; - import type { ModelMessage } from "ai"; --import { PROMPT_CACHE_OPTIONS, STORE_POLICIES } from "lib/ai/instructions"; -+import { STORE_POLICIES } from "lib/ai/instructions"; - import { resolveModel } from "lib/ai/models"; - import { createTools } from "lib/ai/tools"; - import { DEMO_USER } from "lib/demo-user"; -@@ -53,7 +53,6 @@ export function streamAnswer({ +@@ -24,12 +24,6 @@ function resolveModel(): string { + : "anthropic/claude-sonnet-5"; + } + +-// The breakpoint caches the tool definitions plus the system prompt; without +-// it Anthropic caches nothing. +-const PROMPT_CACHE_OPTIONS = { +- openrouter: { cacheControl: { type: "ephemeral" } }, +-}; +- + const TODAY = new Date().toISOString().slice(0, 10); + + // Anthropic only caches prompt prefixes of at least 1024 tokens (Sonnet 5), +@@ -66,7 +60,6 @@ export function streamAnswer({ instructions: { role: "system", - content: `${assistantInstructions}\n\n${STORE_POLICIES}`, + content: SYSTEM_PROMPT, - providerOptions: PROMPT_CACHE_OPTIONS, }, messages, diff --git a/apps/slack-agent/scripts/db-seed.ts b/apps/slack-agent/scripts/db-seed.ts new file mode 100644 index 0000000..8fa3d1d --- /dev/null +++ b/apps/slack-agent/scripts/db-seed.ts @@ -0,0 +1,65 @@ +import { config } from "dotenv"; +config({ path: ".env.local" }); + +import { Pool } from "pg"; +import { generateWarehouse } from "../src/analytics/generate.ts"; +import { strictSslMode } from "../src/analytics/store.ts"; + +const BATCH_SIZE = 500; + +function todayIso(): string { + const d = new Date(); + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +async function seed() { + const connectionString = process.env.DATABASE_URL; + if (!connectionString) { + throw new Error( + "DATABASE_URL is not set. Copy the storefront's Neon URL into apps/slack-agent/.env.local and run npm run db:seed.", + ); + } + const pool = new Pool({ connectionString: strictSslMode(connectionString) }); + + await pool.query(` + CREATE TABLE IF NOT EXISTS analytics_daily ( + metric text NOT NULL, date date NOT NULL, dimension text, dimension_value text, value double precision NOT NULL + ); + `); + await pool.query( + `CREATE INDEX IF NOT EXISTS analytics_daily_metric_date ON analytics_daily (metric, date);`, + ); + await pool.query(`TRUNCATE analytics_daily;`); + + const endDate = todayIso(); + const rows = generateWarehouse(endDate); + + for (let i = 0; i < rows.length; i += BATCH_SIZE) { + const batch = rows.slice(i, i + BATCH_SIZE); + const values: unknown[] = []; + const placeholders = batch.map((row, j) => { + const base = j * 5; + values.push(row.metric, row.date, row.dimension, row.dimensionValue, row.value); + return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5})`; + }); + await pool.query( + `INSERT INTO analytics_daily (metric, date, dimension, dimension_value, value) VALUES ${placeholders.join(", ")}`, + values, + ); + } + + const dates = rows.map((row) => row.date).sort(); + console.log(`Seeded ${rows.length} rows, ${dates[0]} to ${dates[dates.length - 1]}.`); + + await pool.end(); +} + +seed() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error.message ?? error); + process.exit(1); + }); diff --git a/apps/slack-agent/slack-app-manifest.yaml b/apps/slack-agent/slack-app-manifest.yaml index 1eb0f95..2c77874 100644 --- a/apps/slack-agent/slack-app-manifest.yaml +++ b/apps/slack-agent/slack-app-manifest.yaml @@ -1,24 +1,24 @@ display_information: - name: Acme Shopping Assistant + name: Lighthouse Analytics features: bot_user: - display_name: acme-shop + display_name: lighthouse-analytics always_online: true app_home: home_tab_enabled: false messages_tab_enabled: true messages_tab_read_only_enabled: false agent_view: - agent_description: "Answers questions about Acme Store products, orders, and refunds." + agent_description: "Answers product analytics questions from the team's metrics warehouse." suggested_prompts: - - title: "Where is my order?" - message: "Where is my latest order?" - - title: "Find a hoodie" - message: "Show me hoodies under $60" - - title: "Loyalty points" - message: "How many loyalty points do I have?" - - title: "Refund an order" - message: "Can I get a refund for order 1029?" + - title: "Signups last week" + message: "How did signups do last week compared with the week before?" + - title: "Checkout conversion" + message: "What is checkout conversion by device over the last 30 days?" + - title: "Top movers" + message: "What changed the most in page views by path this month?" + - title: "Error rate by release" + message: "Compare error rate across the last two releases." oauth_config: scopes: bot: diff --git a/apps/slack-agent/src/agent.ts b/apps/slack-agent/src/agent.ts index cb03988..b4285c1 100644 --- a/apps/slack-agent/src/agent.ts +++ b/apps/slack-agent/src/agent.ts @@ -1,67 +1,84 @@ import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { isStepCount, streamText } from "ai"; import type { ModelMessage } from "ai"; -import { PROMPT_CACHE_OPTIONS, STORE_POLICIES } from "lib/ai/instructions"; -import { resolveModel } from "lib/ai/models"; -import { createTools } from "lib/ai/tools"; -import { DEMO_USER } from "lib/demo-user"; +import { analyticsTools } from "./analytics/tools.ts"; +import { renderCatalog } from "./analytics/store.ts"; +// The API key is only read when a request is actually made (inside +// baseConfig.headers, lazily), so constructing the client here has no side +// effect - agent.ts stays safe to import without OPENROUTER_API_KEY set, for +// example from the store test that only wants SYSTEM_PROMPT. const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); -// Same guidance as the storefront's chat route, adapted for Slack: app.ts -// adds a card for each product whose exact title is in the reply and a table -// row for each order whose id is in the reply. -const assistantInstructions = - "You are the shopping assistant for Acme Store, answering in Slack. " + - "Use searchProducts to find products (search with product-type keywords " + - 'like "hoodie" or "mug", or browse a collection), getProduct for one ' + - "specific product, and getAccountInfo for anything about the customer's " + - "account, orders, or loyalty points. Use refundOrder when the customer " + - "asks to refund or return an order - confirm which order first, then call " + - "it with the order id. If refundOrder errors, apologize briefly and say " + - "the team has been notified; never retry it. Each product you name by " + - "its exact title gets a card with its price and description under your " + - "reply, so name the products in one short sentence and do not list their " + - "details. Each order you name by its id gets a table row with its status " + - "and total under your reply, so name the orders and do not repeat those " + - "details. " + - "Prices are in USD. " + - "Format with standard markdown: **bold** for emphasis and short bullet " + - "lists, never headings or tables. " + - "Be concise and friendly."; +const SUPPORTED_MODELS = [ + "anthropic/claude-sonnet-5", + "anthropic/claude-haiku-4.5", + "openai/gpt-5-mini", + "google/gemini-2.5-flash", +] as const; -// Labels shown next to each tool's task_update chunk while it runs, so -// Slack's task timeline reads like a sentence instead of a function name. -export const TOOL_TITLES: Record = { - searchProducts: "Searching the catalog", - getProduct: "Looking up the product", - getAccountInfo: "Checking the account", - refundOrder: "Processing the refund", +function resolveModel(): string { + const configured = process.env.OPENROUTER_MODEL; + return configured && (SUPPORTED_MODELS as readonly string[]).includes(configured) + ? configured + : "anthropic/claude-sonnet-5"; +} + +// The breakpoint caches the tool definitions plus the system prompt; without +// it Anthropic caches nothing. +const PROMPT_CACHE_OPTIONS = { + openrouter: { cacheControl: { type: "ephemeral" } }, }; +const TODAY = new Date().toISOString().slice(0, 10); + +// Anthropic only caches prompt prefixes of at least 1024 tokens (Sonnet 5), +// so this prompt is written long and specific on purpose: the rules plus the +// metric catalog and glossary below comfortably clear that floor, and stay +// identical turn to turn so the cache actually hits. +const RULES = `You are the product analytics assistant for the Lighthouse team, in Slack. Lighthouse is a fictional SaaS product; the numbers you report come from Lighthouse's own metrics warehouse, not from the public internet or from anything you already know. + +Rules for every answer: + +- Always call a tool before you state any number. Never guess, round from memory, or reuse a number from earlier in the conversation without a fresh tool call - the warehouse is the only source of truth, and a number you didn't just look up is not a real answer. +- Every time you state a number, say which metric it is and the exact date range you used to get it, in the sentence itself - for example "signups from 2026-09-01 to 2026-09-07 were 1,412", not just "signups were 1,412". A number without its metric and range is not useful to someone reading the thread later. +- When someone asks how a metric "did" over a period - better, worse, up, down, flat - always compare it with the immediately preceding period of the same length using compare_periods, rather than reporting the raw total for the period asked about on its own. "How did signups do last week" means last week compared with the week before, every time. +- Today's date is ${TODAY}. The warehouse keeps ninety days of history ending today. A query that reaches further back than that, or that asks for a range longer than ninety days, fails; when that happens, say so and offer a narrower range that would fit, rather than trying a slightly different range yourself and hoping it works. +- Write every reply in Slack's mrkdwn, not standard markdown: *bold* for emphasis, short bullet lists with a leading dash, and nothing else. Never use markdown headings (# or ##) or tables - Slack does not render either one, so they would show up as literal characters in the message. +- A chart and a table built from your tool results are attached under your reply automatically. Keep the reply to the headline numbers and what they mean; do not list per-day or per-group values, the attachment shows them. +- If a tool call fails, say plainly what failed - the metric, the range, or the dimension you tried to group by - and offer a narrower or different range instead. Never repeat the exact same failing query a second time in one turn; if the user wants another attempt, wait for them to ask or to give you a new range. +- Be concise. Lead with the number and the comparison, then at most one or two sentences of context pulled from the metric's glossary below if it changes how the number should be read.`; + +export const SYSTEM_PROMPT = `${RULES}\n\n${renderCatalog()}`; + export function streamAnswer({ messages, conversationId, abortSignal, + recordContent, }: { messages: ModelMessage[]; conversationId: string; abortSignal?: AbortSignal; + recordContent: boolean; }) { return streamText({ model: openrouter.chat(resolveModel()), instructions: { role: "system", - content: `${assistantInstructions}\n\n${STORE_POLICIES}`, + content: SYSTEM_PROMPT, providerOptions: PROMPT_CACHE_OPTIONS, }, messages, abortSignal, - tools: createTools(DEMO_USER.id, conversationId), - stopWhen: isStepCount(5), + tools: analyticsTools, + stopWhen: isStepCount(6), // functionId names the agent in the AI SDK's telemetry. + // A per-call boolean beats the `dataCollection` default set in instrument.ts. telemetry: { - functionId: "slack-shopping-assistant", + functionId: "slack-analytics-assistant", + recordInputs: recordContent, + recordOutputs: recordContent, }, }); } diff --git a/apps/slack-agent/src/analytics/blocks.test.ts b/apps/slack-agent/src/analytics/blocks.test.ts new file mode 100644 index 0000000..d0df700 --- /dev/null +++ b/apps/slack-agent/src/analytics/blocks.test.ts @@ -0,0 +1,178 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { answerBlocks, fmtChange, type ToolOutcome } from "./blocks.ts"; + +function isoDate(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function daysAgo(n: number): string { + const d = new Date(); + d.setDate(d.getDate() - n); + return isoDate(d); +} + +// Reads the Chart.js config a chartUrl() call encoded into its `c` param. +function decodedChartConfig(imageUrl: string): { labels: string[]; datasets: unknown[] } { + const query = new URL(imageUrl).searchParams; + return JSON.parse(query.get("c")!).data; +} + +test("compare_periods: 7-day series produces a card with a two-dataset chart", () => { + const outcomes: ToolOutcome[] = [ + { + toolName: "compare_periods", + input: { metric: "signups", current: {}, previous: {} }, + output: { + metric: "signups", + current: { from: daysAgo(6), to: daysAgo(0), total: 1500 }, + previous: { from: daysAgo(13), to: daysAgo(7), total: 1300 }, + change: { absolute: 200, percent: 15.4 }, + series: { + current: [200, 210, 220, 230, 210, 200, 230], + previous: [180, 190, 200, 190, 180, 180, 180], + }, + }, + }, + ]; + + const blocks = answerBlocks(outcomes); + const card = blocks[0] as { type: string; title: { text: string }; hero_image?: { image_url: string } }; + assert.equal(card.type, "card"); + assert.ok(card.title.text.includes("▲")); + assert.ok(card.hero_image, "expected a hero_image"); + assert.ok(card.hero_image!.image_url.startsWith("https://quickchart.io/chart?")); + assert.ok(card.hero_image!.image_url.length < 3000); + + const data = decodedChartConfig(card.hero_image!.image_url); + assert.equal(data.datasets.length, 2); +}); + +test("compare_periods: a 90-day series is bucketed to stay under the URL limit", () => { + const days = 90; + const series = Array.from({ length: days }, (_, i) => 100 + i); + const outcomes: ToolOutcome[] = [ + { + toolName: "compare_periods", + input: { metric: "active_users", current: {}, previous: {} }, + output: { + metric: "active_users", + current: { from: daysAgo(days - 1), to: daysAgo(0), total: 9000 }, + previous: { from: daysAgo(2 * days - 1), to: daysAgo(days), total: 8500 }, + change: { absolute: 500, percent: 5.9 }, + series: { current: series, previous: series }, + }, + }, + ]; + + const blocks = answerBlocks(outcomes); + const card = blocks[0] as { hero_image?: { image_url: string } }; + assert.ok(card.hero_image, "expected a hero_image after bucketing"); + + const data = decodedChartConfig(card.hero_image!.image_url); + assert.equal(data.labels.length, 13); +}); + +test("query_metric: grouped output produces a card and a table of groups", () => { + const outcomes: ToolOutcome[] = [ + { + toolName: "query_metric", + input: {}, + output: { + metric: "signups", + from: daysAgo(6), + to: daysAgo(0), + groupBy: "plan", + rowCount: 21, + total: 1500, + rows: [ + { date: daysAgo(6), value: 100, plan: "free" }, + { date: daysAgo(6), value: 50, plan: "team" }, + { date: daysAgo(6), value: 20, plan: "enterprise" }, + ], + groupTotals: [ + { plan: "free", total: 900, share: 0.6 }, + { plan: "team", total: 450, share: 0.3 }, + { plan: "enterprise", total: 150, share: 0.1 }, + ], + }, + }, + ]; + + const blocks = answerBlocks(outcomes); + assert.equal(blocks[0].type, "card"); + const table = blocks[1] as { type: string; rows: { text: string }[][] }; + assert.equal(table.type, "table"); + assert.equal(table.rows.length, 4); + for (const dataRow of table.rows.slice(1)) { + assert.ok(dataRow[2].text.endsWith("%")); + } +}); + +test("top_movers: 12 movers produces a 11-row table and a bar chart card", () => { + const movers = Array.from({ length: 12 }, (_, i) => ({ + device: `device-${i}`, + before: 100 - i, + after: 100 - i * 2, + change: { absolute: -i, percent: -i * 1.5 }, + })); + const outcomes: ToolOutcome[] = [ + { + toolName: "top_movers", + input: {}, + output: { + metric: "checkout_conversion", + dimension: "device", + from: daysAgo(13), + to: daysAgo(0), + movers, + }, + }, + ]; + + const blocks = answerBlocks(outcomes); + const table = blocks[0] as { type: string; rows: unknown[][] }; + assert.equal(table.type, "table"); + assert.equal(table.rows.length, 11); + + const card = blocks[1] as { type: string; hero_image?: { image_url: string } }; + assert.equal(card.type, "card"); + assert.ok(card.hero_image, "expected a hero_image"); + const config = JSON.parse(new URL(card.hero_image!.image_url).searchParams.get("c")!); + assert.equal(config.type, "bar"); + assert.equal(config.options.indexAxis, "y"); +}); + +test("fmtChange formats a negative rate change with points and a real minus sign", () => { + assert.equal( + fmtChange({ absolute: -0.004, percent: -12.5 }, "rate"), + "▼ −0.4 pt (−12.5%)", + ); +}); + +test("an empty query or movers result adds no blocks instead of throwing", () => { + const outcomes: ToolOutcome[] = [ + { + toolName: "query_metric", + input: { metric: "signups", from: daysAgo(7), to: daysAgo(1) }, + output: { + metric: "signups", + from: daysAgo(7), + to: daysAgo(1), + groupBy: null, + rowCount: 0, + total: 0, + rows: [], + }, + }, + { + toolName: "top_movers", + input: { metric: "signups", dimension: "plan", from: daysAgo(7), to: daysAgo(1) }, + output: { metric: "signups", dimension: "plan", from: daysAgo(7), to: daysAgo(1), movers: [] }, + }, + ]; + assert.deepEqual(answerBlocks(outcomes), []); +}); diff --git a/apps/slack-agent/src/analytics/blocks.ts b/apps/slack-agent/src/analytics/blocks.ts new file mode 100644 index 0000000..ed14ce6 --- /dev/null +++ b/apps/slack-agent/src/analytics/blocks.ts @@ -0,0 +1,505 @@ +// Turns this turn's tool results into the chart-and-table attachment that +// closes every reply. The chart is a QuickChart image: the bot only builds +// the URL, Slack's own servers fetch the pixels, so there is no HTTP call +// here and no risk of the chart slowing down the reply. + +import type { CardBlock, KnownBlock, RawTextElement, TableBlock } from "@slack/types"; +import { dailySeries } from "./tools.ts"; +import { METRICS, type MetricDef, type Row } from "./store.ts"; + +export type ToolOutcome = { toolName: string; input: unknown; output: unknown }; + +type ChangeOf = { absolute: number; percent: number | null }; + +// The last 6 outcomes are the ones a reader can still connect to the text +// above them; anything older is history the reply already summarized. +const MAX_OUTCOMES = 6; + +const CURRENT_COLOR = "#6C5FC7"; +const PREVIOUS_COLOR = "#9E9AB8"; +const GROUP_COLORS = ["#6C5FC7", "#F5A623", "#2BA3B3", "#E1567C", "#7BC96F", "#9E9AB8"]; +const MAX_CHART_URL_LENGTH = 3000; +const MAX_MRKDWN_LENGTH = 200; +const DOWNSAMPLE_THRESHOLD = 45; +const BUCKET_SIZE = 7; + +export function answerBlocks(outcomes: ToolOutcome[]): KnownBlock[] { + const blocks: KnownBlock[] = []; + for (const outcome of outcomes.slice(-MAX_OUTCOMES)) { + if (typeof outcome.output !== "object" || outcome.output === null) continue; + switch (outcome.toolName) { + case "compare_periods": + blocks.push(...comparePeriodsBlocks(outcome.output as CompareOutput, outcome.input)); + break; + case "query_metric": + blocks.push(...queryMetricBlocks(outcome.output as QueryOutput)); + break; + case "top_movers": + blocks.push(...topMoversBlocks(outcome.output as TopMoversOutput)); + break; + case "list_metrics": + blocks.push(...listMetricsBlocks(outcome.output as ListMetricsOutput)); + break; + default: + break; + } + } + return blocks; +} + +// Chart.js v4 config, url-encoded into a QuickChart GET request. Slack shows a +// card hero image in a 4:3 box and crops anything wider, so the chart is drawn +// at that ratio, small enough that its fonts stay readable once scaled. Block Kit +// rejects an image URL of 3000 characters or more, so a chart that would +// need one is dropped rather than sent broken. +export function chartUrl( + config: object, + size: { w: number; h: number } = { w: 400, h: 300 }, +): string | undefined { + const url = + `https://quickchart.io/chart?v=4&bkg=white&w=${size.w}&h=${size.h}` + + `&c=${encodeURIComponent(JSON.stringify(config))}`; + return url.length < MAX_CHART_URL_LENGTH ? url : undefined; +} + +// ---- formatting helpers ----------------------------------------------- + +function label(metric: string): string { + const spaced = metric.replace(/_/g, " "); + return spaced.charAt(0).toUpperCase() + spaced.slice(1); +} + +function capitalize(word: string): string { + return word.charAt(0).toUpperCase() + word.slice(1); +} + +function fmt(value: number, unit: MetricDef["unit"]): string { + if (unit === "rate") return `${(value * 100).toFixed(1)}%`; + return new Intl.NumberFormat("en-US").format(Math.round(value)); +} + +function fmtShare(share: number): string { + return `${(share * 100).toFixed(1)}%`; +} + +// Intl formats a negative sign as a plain hyphen; swap it for a real minus +// sign so the number reads right in Slack's font. +function signed(value: number, decimals: number): string { + const formatted = new Intl.NumberFormat("en-US", { + signDisplay: "exceptZero", + minimumFractionDigits: decimals, + maximumFractionDigits: decimals, + }).format(value); + return formatted.replace("-", "−"); +} + +export function fmtChange(change: ChangeOf, unit: MetricDef["unit"]): string { + const marker = change.absolute > 0 ? "▲" : change.absolute < 0 ? "▼" : "–"; + const percentText = change.percent === null ? "" : ` (${signed(change.percent, 1)}%)`; + if (unit === "rate") { + return `${marker} ${signed(change.absolute * 100, 1)} pt${percentText}`; + } + return `${marker} ${signed(Math.round(change.absolute), 0)}${percentText}`; +} + +function isoDate(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function rangeDaysOf(from: string, to: string): number { + const a = new Date(`${from}T00:00:00`).getTime(); + const b = new Date(`${to}T00:00:00`).getTime(); + return Math.round((b - a) / 86_400_000) + 1; +} + +function datesInRange(from: string, to: string): string[] { + const dates: string[] = []; + const cursor = new Date(`${from}T00:00:00`); + const end = new Date(`${to}T00:00:00`); + while (cursor <= end) { + dates.push(isoDate(cursor)); + cursor.setDate(cursor.getDate() + 1); + } + return dates; +} + +function rangeLabel(from: string, to: string): string { + const fromDate = new Date(`${from}T00:00:00`); + const toDate = new Date(`${to}T00:00:00`); + const month = (d: Date) => d.toLocaleDateString("en-US", { month: "short" }); + const fromText = `${month(fromDate)} ${fromDate.getDate()}`; + const toText = `${month(toDate)} ${toDate.getDate()}`; + if (fromDate.getFullYear() !== toDate.getFullYear()) { + return `${fromText}, ${fromDate.getFullYear()} to ${toText}, ${toDate.getFullYear()}`; + } + return `${fromText} to ${toText}, ${toDate.getFullYear()}`; +} + +// x-axis tick label for one date, no year: "Sep 21". +function xLabel(date: string): string { + return new Date(`${date}T00:00:00`).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); +} + +function unitOf(metric: string): MetricDef["unit"] { + return METRICS.find((m) => m.name === metric)?.unit ?? "count"; +} + +// mrkdwn text objects for card title/subtitle/body all share the same +// 200-character ceiling, so every one of them goes through here. +function mrkdwn(text: string): { type: "mrkdwn"; text: string } { + const clipped = text.length > MAX_MRKDWN_LENGTH ? `${text.slice(0, MAX_MRKDWN_LENGTH - 1)}…` : text; + return { type: "mrkdwn", text: clipped }; +} + +function row(...cells: string[]): RawTextElement[] { + return cells.map((text) => ({ type: "raw_text", text })); +} + +function heroImage(url: string | undefined, altText: string): Pick { + return url ? { hero_image: { type: "image", image_url: url, alt_text: altText } } : {}; +} + +// A week of consecutive points collapses into one, dated by the week's first +// day: summed for a count metric, averaged for a rate. Keeps a 90-day chart +// under the QuickChart URL limit. +function weeklyBuckets( + points: { date: string; value: number }[], + unit: MetricDef["unit"], +): { date: string; value: number }[] { + const buckets: { date: string; value: number }[] = []; + for (let i = 0; i < points.length; i += BUCKET_SIZE) { + const chunk = points.slice(i, i + BUCKET_SIZE); + const sum = chunk.reduce((acc, p) => acc + p.value, 0); + buckets.push({ date: chunk[0].date, value: unit === "rate" ? sum / chunk.length : sum }); + } + return buckets; +} + +type LineSeries = { + label: string; + points: { date: string; value: number }[]; + color: string; + dashed?: boolean; +}; + +// One line chart config shared by compare_periods and query_metric. Every +// series in the chart is bucketed the same way, so the lines stay aligned +// point for point even after downsampling. +function lineChartConfig(series: LineSeries[], unit: MetricDef["unit"]): object { + const needsBucketing = series.some((s) => s.points.length > DOWNSAMPLE_THRESHOLD); + const points = needsBucketing + ? series.map((s) => weeklyBuckets(s.points, unit)) + : series.map((s) => s.points); + const labels = (points[0] ?? []).map((p) => xLabel(p.date)); + const scale = unit === "rate" ? 100 : 1; + + return { + type: "line", + data: { + labels, + datasets: series.map((s, i) => ({ + label: s.label, + data: points[i].map((p) => p.value * scale), + borderColor: s.color, + backgroundColor: s.color, + borderDash: s.dashed ? [6, 4] : undefined, + tension: 0.3, + pointRadius: 2, + borderWidth: 2, + fill: false, + })), + }, + options: { + plugins: { legend: { position: "bottom", labels: { font: { size: 14 } } } }, + scales: { + x: { ticks: { font: { size: 13 }, maxRotation: 0, maxTicksLimit: 7 } }, + y: { + grace: "10%", + ticks: { font: { size: 13 } }, + ...(unit === "rate" ? { title: { display: true, text: "%" } } : {}), + }, + }, + }, + }; +} + +function barChartConfig(bars: { label: string; value: number }[]): object { + return { + type: "bar", + data: { + labels: bars.map((b) => b.label), + datasets: [ + { + data: bars.map((b) => b.value), + backgroundColor: "#6C5FC7", + }, + ], + }, + options: { + indexAxis: "y", + plugins: { legend: { display: false } }, + scales: { + x: { title: { display: true, text: "% change" }, ticks: { font: { size: 13 } } }, + y: { ticks: { font: { size: 13 } } }, + }, + }, + }; +} + +// ---- compare_periods ---------------------------------------------------- + +type CompareOutput = { + metric: string; + current: { from: string; to: string; total: number }; + previous: { from: string; to: string; total: number }; + change: ChangeOf; + series: { current: number[]; previous: number[] }; + groups?: (Record & { + current: number; + previous: number; + change: ChangeOf; + })[]; +}; + +function comparePeriodsBlocks(output: CompareOutput, input: unknown): KnownBlock[] { + const unit = unitOf(output.metric); + const currentDates = datesInRange(output.current.from, output.current.to); + const previousDates = datesInRange(output.previous.from, output.previous.to); + const currentPoints = currentDates.map((date, i) => ({ + date, + value: output.series.current[i] ?? 0, + })); + const previousPoints = previousDates.map((date, i) => ({ + date, + value: output.series.previous[i] ?? 0, + })); + + const config = lineChartConfig( + [ + { + label: rangeLabel(output.current.from, output.current.to), + points: currentPoints, + color: CURRENT_COLOR, + }, + { + label: rangeLabel(output.previous.from, output.previous.to), + points: previousPoints, + color: PREVIOUS_COLOR, + dashed: true, + }, + ], + unit, + ); + const url = chartUrl(config); + + const card: CardBlock = { + type: "card", + ...heroImage(url, `${label(output.metric)} chart`), + title: mrkdwn(`*${label(output.metric)}* ${fmtChange(output.change, unit)}`), + subtitle: mrkdwn( + `${rangeLabel(output.current.from, output.current.to)} vs ${rangeLabel(output.previous.from, output.previous.to)}`, + ), + body: mrkdwn( + `Current ${fmt(output.current.total, unit)} · Previous ${fmt(output.previous.total, unit)}`, + ), + }; + + const blocks: KnownBlock[] = [card]; + + if (output.groups) { + const groupBy = (input as { groupBy?: string } | undefined)?.groupBy ?? "group"; + const sorted = [...output.groups] + .sort((a, b) => Math.abs(b.change.absolute) - Math.abs(a.change.absolute)) + .slice(0, 10); + const table: TableBlock = { + type: "table", + column_settings: [{}, { align: "right" }, { align: "right" }, { align: "right" }], + rows: [ + row(capitalize(groupBy), "Current", "Previous", "Change"), + ...sorted.map((g) => + row( + String(g[groupBy]), + fmt(g.current, unit), + fmt(g.previous, unit), + fmtChange(g.change, unit), + ), + ), + ], + }; + blocks.push(table); + } + + return blocks; +} + +// ---- query_metric --------------------------------------------------------- + +type GroupTotal = Record & { total: number; share: number | null }; + +type QueryOutput = { + metric: string; + from: string; + to: string; + groupBy: string | null; + rowCount: number; + total: number; + rows: Row[]; + groupTotals?: GroupTotal[]; +}; + +function queryMetricBlocks(output: QueryOutput): KnownBlock[] { + const unit = unitOf(output.metric); + if (output.rows.length === 0) return []; + const rangeDays = rangeDaysOf(output.from, output.to); + const truncated = output.rowCount > output.rows.length; + const verb = unit === "rate" ? "Average" : "Total"; + const subtitleText = + `${verb} ${fmt(output.total, unit)} over ${rangeDays} days` + + (truncated ? ` (first ${output.rows.length} rows)` : ""); + + let bodyText: string; + let config: object; + + if (output.groupBy && output.groupTotals) { + const groupBy = output.groupBy; + const topGroups = output.groupTotals.slice(0, 6); + const top = output.groupTotals[0]; + bodyText = + `${output.groupTotals.length} ${groupBy}s · top: ${String(top[groupBy])} ${fmt(top.total, unit)}` + + (top.share === null ? "" : ` (${fmtShare(top.share)})`); + config = lineChartConfig( + topGroups.map((g, i) => ({ + label: String(g[groupBy]), + points: dailySeries( + output.rows.filter((r) => r[groupBy] === g[groupBy]), + unit, + ), + color: GROUP_COLORS[i % GROUP_COLORS.length], + })), + unit, + ); + } else { + const series = dailySeries(output.rows, unit); + const peak = series.reduce((a, b) => (b.value > a.value ? b : a)); + const low = series.reduce((a, b) => (b.value < a.value ? b : a)); + bodyText = `Peak ${fmt(peak.value, unit)} on ${xLabel(peak.date)} · Low ${fmt(low.value, unit)} on ${xLabel(low.date)}`; + config = lineChartConfig( + [{ label: label(output.metric), points: series, color: CURRENT_COLOR }], + unit, + ); + } + + const url = chartUrl(config); + const card: CardBlock = { + type: "card", + ...heroImage(url, `${label(output.metric)} chart`), + title: mrkdwn(`*${label(output.metric)}* · ${rangeLabel(output.from, output.to)}`), + subtitle: mrkdwn(subtitleText), + body: mrkdwn(bodyText), + }; + + const blocks: KnownBlock[] = [card]; + + if (output.groupBy && output.groupTotals) { + const groupBy = output.groupBy; + const rows = output.groupTotals.slice(0, 10); + const table: TableBlock = + unit === "rate" + ? { + type: "table", + column_settings: [{}, { align: "right" }], + rows: [ + row(capitalize(groupBy), "Average"), + ...rows.map((g) => row(String(g[groupBy]), fmt(g.total, unit))), + ], + } + : { + type: "table", + column_settings: [{}, { align: "right" }, { align: "right" }], + rows: [ + row(capitalize(groupBy), "Total", "Share"), + ...rows.map((g) => + row(String(g[groupBy]), fmt(g.total, unit), fmtShare(g.share ?? 0)), + ), + ], + }; + blocks.push(table); + } + + return blocks; +} + +// ---- top_movers ----------------------------------------------------------- + +type Mover = Record & { before: number; after: number; change: ChangeOf }; + +type TopMoversOutput = { + metric: string; + dimension: string; + from: string; + to: string; + movers: Mover[]; +}; + +function topMoversBlocks(output: TopMoversOutput): KnownBlock[] { + const unit = unitOf(output.metric); + if (output.movers.length === 0) return []; + const dimension = output.dimension; + const rows = output.movers.slice(0, 10); + const table: TableBlock = { + type: "table", + column_settings: [{}, { align: "right" }, { align: "right" }, { align: "right" }], + rows: [ + row(capitalize(dimension), "First half", "Second half", "Change"), + ...rows.map((m) => + row( + String(m[dimension]), + fmt(m.before, unit), + fmt(m.after, unit), + fmtChange(m.change, unit), + ), + ), + ], + }; + + const bars = output.movers + .filter((m) => m.change.percent !== null) + .slice(0, 8) + .map((m) => ({ label: String(m[dimension]), value: m.change.percent as number })); + const url = chartUrl(barChartConfig(bars)); + + const biggest = output.movers[0]; + const card: CardBlock = { + type: "card", + ...heroImage(url, `${label(output.metric)} movers chart`), + title: mrkdwn(`*${label(output.metric)} movers by ${dimension}* · ${rangeLabel(output.from, output.to)}`), + subtitle: mrkdwn("Second half vs first half"), + body: mrkdwn(`Biggest mover: ${String(biggest[dimension])} ${fmtChange(biggest.change, unit)}`), + }; + + return [table, card]; +} + +// ---- list_metrics ----------------------------------------------------------- + +type ListMetricsOutput = { + metrics: { name: string; unit: MetricDef["unit"]; dimensions: string[] }[]; +}; + +function listMetricsBlocks(output: ListMetricsOutput): KnownBlock[] { + const table: TableBlock = { + type: "table", + column_settings: [{}, {}, {}], + rows: [ + row("Metric", "Unit", "Dimensions"), + ...output.metrics.map((m) => row(m.name, m.unit, m.dimensions.join(", "))), + ], + }; + return [table]; +} diff --git a/apps/slack-agent/src/analytics/generate.ts b/apps/slack-agent/src/analytics/generate.ts new file mode 100644 index 0000000..69a318f --- /dev/null +++ b/apps/slack-agent/src/analytics/generate.ts @@ -0,0 +1,361 @@ +// The deterministic generator behind the Lighthouse analytics warehouse. +// Every number below comes from a seeded PRNG, so the same endDate always +// produces the same rows: `npm run db:seed` reruns this and reloads +// Postgres, and a presenter who reseeds on the workshop day gets the same +// story, just shifted to end on that day. No Sentry, no pg, no I/O here. + +const RANGE_DAYS = 90; +const SEED = 1337424242; + +// mulberry32: a small, fast, seeded PRNG. Good enough for fake data, not for +// anything that needs real randomness. +function mulberry32(seed: number): () => number { + let t = seed >>> 0; + return () => { + t = (t + 0x6d2b79f5) >>> 0; + let r = Math.imul(t ^ (t >>> 15), 1 | t); + r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r; + return ((r ^ (r >>> 14)) >>> 0) / 4294967296; + }; +} + +// A multiplicative jitter of +/- spread around 1, e.g. noise(rng, 0.1) is +// somewhere between 0.9x and 1.1x. +function noise(rng: () => number, spread: number): number { + return 1 + (rng() - 0.5) * 2 * spread; +} + +function isoDate(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +// Weekend traffic runs lower across every metric here. Built from the real +// day of week, not the day offset, so a Saturday is always a Saturday. +function weekendMultiplier(dateIso: string): number { + const day = new Date(`${dateIso}T00:00:00`).getDay(); + return day === 0 || day === 6 ? 0.8 : 1; +} + +function roundRate(value: number): number { + return Math.round(value * 10000) / 10000; +} + +type DayRecord = { date: string; offsetFromEnd: number }; + +// 90 days ending at endDate, oldest first. Indexed by offsetFromEnd (0 = +// endDate, 89 = the oldest day) rather than by the calendar date, so the +// story below - the release windows, the regression, the recovery - always +// lands on the same relative days no matter which endDate is passed in. +function buildDays(endDate: string): DayRecord[] { + const end = new Date(`${endDate}T00:00:00`); + const days: DayRecord[] = []; + for (let offsetFromEnd = RANGE_DAYS - 1; offsetFromEnd >= 0; offsetFromEnd--) { + const d = new Date(end); + d.setDate(d.getDate() - offsetFromEnd); + days.push({ date: isoDate(d), offsetFromEnd }); + } + return days; +} + +// Only v1.4.2's and v1.4.3's start were specified (12 and 3 days before the +// end); the v1.4.0/v1.4.1 boundary is an even split of the remaining days so +// the four releases tile the full 90-day window with no gaps or overlap. +const RELEASE_WINDOWS = [ + { release: "v1.4.0", minOffset: 51, maxOffset: 89 }, + { release: "v1.4.1", minOffset: 13, maxOffset: 50 }, + { release: "v1.4.2", minOffset: 4, maxOffset: 12 }, + { release: "v1.4.3", minOffset: 0, maxOffset: 3 }, +] as const; + +// The day v1.4.2 shipped. page_views on /pricing and checkout_conversion on +// mobile stay degraded from here through endDate; only error_rate recovers, +// in the v1.4.3 window. +const DEGRADED_SINCE_OFFSET = 12; + +function releaseForOffset(offsetFromEnd: number): string { + const window = RELEASE_WINDOWS.find( + (w) => offsetFromEnd >= w.minOffset && offsetFromEnd <= w.maxOffset, + ); + if (!window) throw new Error(`No release window covers offset ${offsetFromEnd}`); + return window.release; +} + +export type MetricDef = { + name: string; + description: string; + unit: "count" | "rate"; + dimensions: string[]; + glossary: string; +}; + +export const METRICS: MetricDef[] = [ + { + name: "signups", + description: "New account signups per day, by plan and by signup country.", + unit: "count", + dimensions: ["plan", "country"], + glossary: + "signups counts a new account the moment it verifies its email, in the " + + "region where the day boundary is UTC-8 (Lighthouse's primary market). " + + "The plan dimension is the plan the account chose at signup, not its " + + "current plan - a free signup that upgrades a week later still counts " + + "as a free signup that day, because upgrades show up in a separate " + + "revenue metric, not here. The country dimension comes from the " + + "signup form's billing country field, not IP geolocation, so it is " + + "accurate but self-reported. Expect signups to dip on weekends and on " + + "major US holidays; a dip on an ordinary weekday is the signal worth " + + "chasing, a dip on a Saturday is not.", + }, + { + name: "active_users", + description: "Daily active users by platform (web, iOS, Android).", + unit: "count", + dimensions: ["platform"], + glossary: + "active_users counts an authenticated session with at least one " + + "in-app action that day, per platform. A person who opens both the " + + "web app and the iOS app on the same day counts once in each " + + "platform's total, so summing the three platforms overstates true " + + "unique headcount by the size of that overlap; use this metric to " + + "compare a platform against its own history, not to add the " + + "platforms together. A native app store update can gate a release " + + "behind review for a day or two, which shows up here as a temporary " + + "dip in ios or android even when nothing in the product changed.", + }, + { + name: "page_views", + description: "Page views on the marketing site and app shell, by path.", + unit: "count", + dimensions: ["path"], + glossary: + "page_views counts a rendered page load after bot and crawler traffic " + + "is filtered out, by path. /pricing is the page with the clearest " + + "purchase intent, so a drop there is worth more attention than an " + + "equivalent drop on /blog. Because this metric counts views of a " + + "path, not a fixed page, a release that restructures the pricing " + + "page (a new URL, a redirect, a modal instead of a page) can move " + + "this number on its own, with no change in how many people actually " + + "wanted to see pricing.", + }, + { + name: "checkout_conversion", + description: "Share of checkout sessions that complete purchase, by device.", + unit: "rate", + dimensions: ["device"], + glossary: + "checkout_conversion is completed purchases divided by checkout " + + "sessions started, by device - the denominator is people who already " + + "reached checkout, not every site visitor, so this tracks the " + + "checkout flow itself rather than general purchase interest. Mobile " + + "conversion normally runs lower than desktop; the two devices are not " + + "expected to match. A regression introduced in the mobile checkout " + + "flow - a broken form field, a slow payment step - shows up here " + + "before it shows up anywhere in reported revenue, because revenue " + + "lags a day or two behind the sessions that produced it.", + }, + { + name: "error_rate", + description: "Application error rate (5xx and unhandled client errors) by release.", + unit: "rate", + dimensions: ["release"], + glossary: + "error_rate is errored requests divided by total requests, for " + + "whichever release was serving traffic that day. In this warehouse " + + "exactly one release serves all traffic on any given day - there is " + + "no canary or gradual rollout to blend into the number - so a spike " + + "right after a release starts almost always means the release " + + "itself, not a coincidence. A release's window begins the day it " + + "ships and ends the day the next one ships; querying error_rate " + + "grouped by release compares whole releases against each other, not " + + "individual days.", + }, +]; + +type SeriesRow = { date: string; value: number }; + +type ShareSpec = { + value: string; + share: number; + // Multiplier applied on top of the day's baseline share, keyed by how + // many days ago the day is. Used for the release-4-onward degradations. + storyMultiplier?: (offsetFromEnd: number) => number; +}; + +type MetricSeries = { + total: SeriesRow[]; + byDimension: Record>; +}; + +function buildCountSeries( + days: DayRecord[], + rng: () => number, + baseline: number, + noiseSpread: number, + dimensions: Record, +): MetricSeries { + const total: SeriesRow[] = []; + const byDimension: Record> = {}; + for (const [dim, specs] of Object.entries(dimensions)) { + byDimension[dim] = {}; + for (const spec of specs) byDimension[dim][spec.value] = []; + } + + for (const day of days) { + const dayValue = Math.round( + baseline * weekendMultiplier(day.date) * noise(rng, noiseSpread), + ); + total.push({ date: day.date, value: dayValue }); + + for (const [dim, specs] of Object.entries(dimensions)) { + for (const spec of specs) { + const storyFactor = spec.storyMultiplier?.(day.offsetFromEnd) ?? 1; + const value = Math.round( + dayValue * spec.share * storyFactor * noise(rng, noiseSpread), + ); + byDimension[dim][spec.value].push({ date: day.date, value }); + } + } + } + + return { total, byDimension }; +} + +function buildCheckoutConversion(days: DayRecord[], rng: () => number): MetricSeries { + const devices = [ + { value: "desktop", base: 0.05, trafficShare: 0.6 }, + { + value: "mobile", + base: 0.032, + trafficShare: 0.4, + storyMultiplier: (offsetFromEnd: number) => + offsetFromEnd <= DEGRADED_SINCE_OFFSET ? 0.8 : 1, + }, + ]; + + const total: SeriesRow[] = []; + const byDimension: Record> = { device: {} }; + for (const device of devices) byDimension.device[device.value] = []; + + for (const day of days) { + let blended = 0; + for (const device of devices) { + const storyFactor = device.storyMultiplier?.(day.offsetFromEnd) ?? 1; + const value = roundRate( + device.base * weekendMultiplier(day.date) * storyFactor * noise(rng, 0.06), + ); + byDimension.device[device.value].push({ date: day.date, value }); + blended += value * device.trafficShare; + } + total.push({ date: day.date, value: roundRate(blended) }); + } + + return { total, byDimension }; +} + +function buildErrorRate(days: DayRecord[], rng: () => number): MetricSeries { + const baseline = 0.008; + const total: SeriesRow[] = []; + const byDimension: Record> = { release: {} }; + for (const window of RELEASE_WINDOWS) byDimension.release[window.release] = []; + + for (const day of days) { + const release = releaseForOffset(day.offsetFromEnd); + // v1.4.2 doubles the error rate; v1.4.3 recovers it. v1.4.0 and v1.4.1 + // are unaffected baseline. + const storyFactor = release === "v1.4.2" ? 2 : 1; + const value = roundRate( + baseline * weekendMultiplier(day.date) * storyFactor * noise(rng, 0.12), + ); + total.push({ date: day.date, value }); + byDimension.release[release].push({ date: day.date, value }); + } + + return { total, byDimension }; +} + +function buildWarehouse(days: DayRecord[], rng: () => number): Record { + // Built in this fixed order, against one shared rng, so the numbers below + // never change between two calls with the same endDate. + return { + signups: buildCountSeries(days, rng, 220, 0.1, { + plan: [ + { value: "free", share: 0.6 }, + { value: "team", share: 0.3 }, + { value: "enterprise", share: 0.1 }, + ], + country: [ + { value: "US", share: 0.4 }, + { value: "DE", share: 0.15 }, + { value: "FR", share: 0.15 }, + { value: "GB", share: 0.15 }, + { value: "BR", share: 0.15 }, + ], + }), + active_users: buildCountSeries(days, rng, 6000, 0.06, { + platform: [ + { value: "web", share: 0.5 }, + { value: "ios", share: 0.3 }, + { value: "android", share: 0.2 }, + ], + }), + page_views: buildCountSeries(days, rng, 25000, 0.08, { + path: [ + { value: "/", share: 0.35 }, + { + value: "/pricing", + share: 0.15, + storyMultiplier: (offsetFromEnd) => + offsetFromEnd <= DEGRADED_SINCE_OFFSET ? 0.65 : 1, + }, + { value: "/docs", share: 0.2 }, + { value: "/signup", share: 0.1 }, + { value: "/blog", share: 0.15 }, + { value: "/changelog", share: 0.05 }, + ], + }), + checkout_conversion: buildCheckoutConversion(days, rng), + error_rate: buildErrorRate(days, rng), + }; +} + +export type WarehouseRow = { + metric: string; + date: string; + dimension: string | null; + dimensionValue: string | null; + value: number; +}; + +// 90 days ending at endDate inclusive. Totals rows have dimension null; +// per-dimension rows have dimension and dimensionValue set. A fresh rng +// seeded the same way every call, so two calls with the same endDate +// produce the same rows. +export function generateWarehouse(endDate: string): WarehouseRow[] { + const days = buildDays(endDate); + const warehouse = buildWarehouse(days, mulberry32(SEED)); + + const rows: WarehouseRow[] = []; + for (const [metric, series] of Object.entries(warehouse)) { + for (const row of series.total) { + rows.push({ metric, date: row.date, dimension: null, dimensionValue: null, value: row.value }); + } + for (const [dimension, byValue] of Object.entries(series.byDimension)) { + for (const [dimensionValue, dimRows] of Object.entries(byValue)) { + for (const row of dimRows) { + rows.push({ metric, date: row.date, dimension, dimensionValue, value: row.value }); + } + } + } + } + return rows; +} + +export function renderCatalog(): string { + return METRICS.map( + (m) => + `${m.name} (${m.unit}). ${m.description} Dimensions: ${m.dimensions.join(", ")}.\n${m.glossary}`, + ).join("\n\n"); +} diff --git a/apps/slack-agent/src/analytics/store.test.ts b/apps/slack-agent/src/analytics/store.test.ts new file mode 100644 index 0000000..ce6599b --- /dev/null +++ b/apps/slack-agent/src/analytics/store.test.ts @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { SYSTEM_PROMPT } from "../agent.ts"; +import { generateWarehouse } from "./generate.ts"; +import { AnalyticsQueryError, validateQuery } from "./store.ts"; + +function iso(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function daysAgo(n: number): string { + const d = new Date(); + d.setDate(d.getDate() - n); + return iso(d); +} + +const TODAY = iso(new Date()); + +test("generateWarehouse is deterministic for the same endDate", () => { + const first = generateWarehouse(TODAY); + const second = generateWarehouse(TODAY); + assert.deepEqual(first, second); +}); + +test("the v1.4.2 story: error_rate under v1.4.2 is at least 1.6x error_rate under v1.4.1", () => { + const rows = generateWarehouse(TODAY).filter( + (row) => row.metric === "error_rate" && row.dimension === null, + ); + const v142 = rows.filter((row) => row.date >= daysAgo(12) && row.date <= daysAgo(4)); + const v141 = rows.filter((row) => row.date >= daysAgo(50) && row.date <= daysAgo(13)); + const mean = (values: typeof rows) => + values.reduce((sum, row) => sum + row.value, 0) / values.length; + const meanV142 = mean(v142); + const meanV141 = mean(v141); + assert.ok( + meanV142 >= meanV141 * 1.6, + `expected ${meanV142} to be at least 1.6x ${meanV141}`, + ); +}); + +test("an unknown metric is rejected", () => { + assert.throws( + () => validateQuery({ metric: "not_a_real_metric", from: daysAgo(1), to: TODAY }), + AnalyticsQueryError, + ); +}); + +test("a groupBy outside the metric's dimensions is rejected", () => { + assert.throws( + () => + validateQuery({ metric: "signups", from: daysAgo(1), to: TODAY, groupBy: "device" }), + AnalyticsQueryError, + ); +}); + +test("a to date before from is rejected", () => { + assert.throws( + () => validateQuery({ metric: "signups", from: daysAgo(1), to: daysAgo(2) }), + AnalyticsQueryError, + ); +}); + +test("a to date after today is rejected", () => { + const tomorrow = daysAgo(-1); + assert.throws( + () => validateQuery({ metric: "signups", from: daysAgo(1), to: tomorrow }), + AnalyticsQueryError, + ); +}); + +test("a range longer than 90 days is rejected", () => { + assert.throws( + () => validateQuery({ metric: "signups", from: daysAgo(99), to: TODAY }), + AnalyticsQueryError, + ); +}); + +test("SYSTEM_PROMPT is long enough for Anthropic's prompt cache", () => { + assert.ok( + SYSTEM_PROMPT.length >= 5000, + `SYSTEM_PROMPT is ${SYSTEM_PROMPT.length} characters`, + ); +}); diff --git a/apps/slack-agent/src/analytics/store.ts b/apps/slack-agent/src/analytics/store.ts new file mode 100644 index 0000000..54a0480 --- /dev/null +++ b/apps/slack-agent/src/analytics/store.ts @@ -0,0 +1,137 @@ +// The Lighthouse analytics warehouse, now a real Postgres table on Neon: +// scan() runs one SQL query through the `pg` driver, which @sentry/node 11 +// instruments automatically, so every scan gets a `db` child span under the +// `analytics.scan` span in tools.ts. `npm run db:seed` fills the table from +// the deterministic generator in ./generate. + +import { Pool } from "pg"; +import { METRICS, type MetricDef } from "./generate.ts"; + +export { METRICS, renderCatalog, type MetricDef } from "./generate.ts"; + +const RANGE_DAYS = 90; + +export type Row = { date: string; value: number } & Record; + +export class AnalyticsQueryError extends Error { + constructor(message: string) { + super(message); + this.name = "AnalyticsQueryError"; + } +} + +function isoDate(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function todayIso(): string { + return isoDate(new Date()); +} + +function daysBetweenInclusive(from: string, to: string): number { + const a = new Date(`${from}T00:00:00`).getTime(); + const b = new Date(`${to}T00:00:00`).getTime(); + return Math.round((b - a) / 86_400_000) + 1; +} + +export function validateQuery(args: { + metric: string; + from: string; + to: string; + groupBy?: string; +}): { def: MetricDef; rangeDays: number } { + const { metric, from, to, groupBy } = args; + const def = METRICS.find((m) => m.name === metric); + if (!def) { + throw new AnalyticsQueryError( + `Unknown metric "${metric}". Known metrics: ${METRICS.map((m) => m.name).join(", ")}.`, + ); + } + if (groupBy && !def.dimensions.includes(groupBy)) { + throw new AnalyticsQueryError( + `"${groupBy}" is not a dimension of ${metric}. Known dimensions: ${def.dimensions.join(", ")}.`, + ); + } + if (to < from) { + throw new AnalyticsQueryError(`"to" (${to}) is before "from" (${from}).`); + } + const today = todayIso(); + if (to > today) { + throw new AnalyticsQueryError(`"to" (${to}) is after today (${today}).`); + } + const rangeDays = daysBetweenInclusive(from, to); + if (rangeDays > RANGE_DAYS) { + throw new AnalyticsQueryError( + `The range ${from} to ${to} is ${rangeDays} days, outside the 90-day retention window.`, + ); + } + return { def, rangeDays }; +} + +// Created on first scan(), not at import time, so importing this module - +// directly, or through agent.ts for SYSTEM_PROMPT - never requires +// DATABASE_URL or opens a connection. +let pool: Pool | undefined; + +function getPool(): Pool { + if (!pool) { + const connectionString = process.env.DATABASE_URL; + if (!connectionString) { + throw new Error( + "DATABASE_URL is not set. Copy the storefront's Neon URL into apps/slack-agent/.env.local and run npm run db:seed.", + ); + } + pool = new Pool({ connectionString: strictSslMode(connectionString) }); + } + return pool; +} + +export async function scan(args: { + metric: string; + from: string; + to: string; + groupBy?: string; +}): Promise<{ rows: Row[]; rangeDays: number }> { + const { metric, from, to, groupBy } = args; + const { rangeDays } = validateQuery(args); + const client = getPool(); + + if (groupBy) { + const { rows } = await client.query( + `SELECT date::text AS date, dimension_value, value FROM analytics_daily WHERE metric = $1 AND dimension = $4 AND date BETWEEN $2 AND $3 ORDER BY date, dimension_value`, + [metric, from, to, groupBy], + ); + return { + rows: rows.map((row) => ({ + date: row.date, + value: Number(row.value), + [groupBy]: row.dimension_value, + })), + rangeDays, + }; + } + + const { rows } = await client.query( + `SELECT date::text AS date, value FROM analytics_daily WHERE metric = $1 AND dimension IS NULL AND date BETWEEN $2 AND $3 ORDER BY date`, + [metric, from, to], + ); + return { + rows: rows.map((row) => ({ date: row.date, value: Number(row.value) })), + rangeDays, + }; +} + +export function total(rows: Row[], unit: MetricDef["unit"]): number { + if (rows.length === 0) return 0; + const sum = rows.reduce((acc, row) => acc + row.value, 0); + return unit === "rate" ? sum / rows.length : sum; +} + +// pg 8 treats sslmode=require, the mode Neon puts in its URLs, as verify-full +// and warns on every start that the alias changes meaning in pg 9. +export function strictSslMode(connectionString: string): string { + return connectionString.replace(/sslmode=(prefer|require|verify-ca)\b/, "sslmode=verify-full"); +} diff --git a/apps/slack-agent/src/analytics/tools.ts b/apps/slack-agent/src/analytics/tools.ts new file mode 100644 index 0000000..6e67576 --- /dev/null +++ b/apps/slack-agent/src/analytics/tools.ts @@ -0,0 +1,284 @@ +import * as Sentry from "@sentry/node"; +import { tool } from "ai"; +import { z } from "zod"; +import { METRICS, scan, total, type MetricDef, type Row } from "./store.ts"; + +// Labels shown next to each tool's task_update chunk while it runs, keyed by +// the tool names below (app.ts imports this for the Slack task timeline). +export const TOOL_TITLES: Record = { + list_metrics: "Listing the metrics catalog", + query_metric: "Querying the warehouse", + compare_periods: "Comparing two periods", + top_movers: "Finding the top movers", +}; + +function isoDate(d: Date): string { + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +function rangeDaysOf(from: string, to: string): number { + const a = new Date(`${from}T00:00:00`).getTime(); + const b = new Date(`${to}T00:00:00`).getTime(); + return Math.round((b - a) / 86_400_000) + 1; +} + +function unitOf(metric: string): MetricDef["unit"] { + return METRICS.find((m) => m.name === metric)?.unit ?? "count"; +} + +// One entry per distinct date, sorted ascending. A grouped query returns +// several rows per date, so counts are summed and rates are averaged across +// the rows on that date. +export function dailySeries( + rows: Row[], + unit: MetricDef["unit"], +): { date: string; value: number }[] { + const byDate = new Map(); + for (const row of rows) { + const forDate = byDate.get(row.date) ?? []; + forDate.push(row); + byDate.set(row.date, forDate); + } + return [...byDate.keys()].sort().map((date) => ({ + date, + value: total(byDate.get(date)!, unit), + })); +} + +function changeOf( + current: number, + previous: number, +): { absolute: number; percent: number | null } { + return { + absolute: current - previous, + percent: previous === 0 ? null : ((current - previous) / previous) * 100, + }; +} + +// A scan's arguments and row count are the only things that stay useful in +// a trace where inputs and outputs are not recorded - a direct message. So +// every scan gets its own low-cardinality span, wrapped here once for all +// four tools, instead of the full query and its result. +async function scannedRows(args: { + metric: string; + from: string; + to: string; + groupBy?: string; +}): Promise { + return Sentry.startSpan( + { + name: `scan ${args.metric}`, + op: "analytics.scan", + attributes: { + "analytics.metric": args.metric, + "analytics.range_days": rangeDaysOf(args.from, args.to), + "analytics.group_by": args.groupBy ?? "none", + }, + }, + async (span) => { + const { rows } = await scan(args); + span.setAttribute("analytics.row_count", rows.length); + return rows; + }, + ); +} + +function groupTotalsOf(rows: Row[], groupBy: string, unit: MetricDef["unit"]) { + const values = new Set(rows.map((r) => String(r[groupBy]))); + const totals = [...values].map((value) => ({ + [groupBy]: value, + total: total( + rows.filter((r) => r[groupBy] === value), + unit, + ), + })); + const sumOfTotals = totals.reduce((sum, t) => sum + t.total, 0); + return totals + .map((t) => ({ + ...t, + share: unit === "rate" ? null : sumOfTotals === 0 ? 0 : t.total / sumOfTotals, + })) + .sort((a, b) => b.total - a.total); +} + +function totalsByGroup( + currentRows: Row[], + previousRows: Row[], + groupBy: string, + unit: MetricDef["unit"], +) { + const values = new Set([ + ...currentRows.map((r) => String(r[groupBy])), + ...previousRows.map((r) => String(r[groupBy])), + ]); + return [...values].map((value) => { + const current = total( + currentRows.filter((r) => r[groupBy] === value), + unit, + ); + const previous = total( + previousRows.filter((r) => r[groupBy] === value), + unit, + ); + return { [groupBy]: value, current, previous, change: changeOf(current, previous) }; + }); +} + +// The midpoint date that splits [from, to] into two roughly equal halves. +function splitRange(from: string, to: string): { midBefore: string; midAfter: string } { + const halfDays = Math.floor(rangeDaysOf(from, to) / 2); + const fromDate = new Date(`${from}T00:00:00`); + const midBefore = new Date(fromDate); + midBefore.setDate(midBefore.getDate() + halfDays - 1); + const midAfter = new Date(fromDate); + midAfter.setDate(midAfter.getDate() + halfDays); + return { midBefore: isoDate(midBefore), midAfter: isoDate(midAfter) }; +} + +const dateRange = z.object({ + from: z.string().describe("Inclusive start date, YYYY-MM-DD"), + to: z.string().describe("Inclusive end date, YYYY-MM-DD"), +}); + +export const analyticsTools = { + list_metrics: tool({ + description: + "List every metric in the Lighthouse warehouse, with its unit and " + + "the dimensions it can be grouped by. Call this before query_metric, " + + "compare_periods, or top_movers if you are not already sure of the " + + "exact metric name and dimension names.", + inputSchema: z.object({}), + execute: async () => ({ + metrics: METRICS.map(({ name, description, unit, dimensions }) => ({ + name, + description, + unit, + dimensions, + })), + }), + }), + + query_metric: tool({ + description: + "Query one metric over a date range. from and to are inclusive ISO " + + "dates, no more than 90 days apart, and to cannot be after today. " + + "groupBy splits the result into one row per day per value of one of " + + "the metric's dimensions; omit it for one row per day.", + inputSchema: z.object({ + metric: z.string().describe("A metric name from list_metrics"), + from: z.string().describe("Inclusive start date, YYYY-MM-DD"), + to: z.string().describe("Inclusive end date, YYYY-MM-DD"), + groupBy: z + .string() + .optional() + .describe("One of the metric's dimensions, from list_metrics"), + }), + execute: async ({ metric, from, to, groupBy }) => { + const unit = unitOf(metric); + const rows = await scannedRows({ metric, from, to, groupBy }); + return { + metric, + from, + to, + groupBy: groupBy ?? null, + rowCount: rows.length, + total: total(rows, unit), + rows: rows.slice(0, 200), + groupTotals: groupBy ? groupTotalsOf(rows, groupBy, unit) : undefined, + }; + }, + }), + + compare_periods: tool({ + description: + "Compare one metric's total between a current date range and a " + + "previous date range. Returns the total for each period, the " + + "absolute change, and the percent change. Splits by one dimension " + + "when groupBy is given.", + inputSchema: z.object({ + metric: z.string().describe("A metric name from list_metrics"), + current: dateRange, + previous: dateRange, + groupBy: z + .string() + .optional() + .describe("One of the metric's dimensions, from list_metrics"), + }), + execute: async ({ metric, current, previous, groupBy }) => { + const unit = unitOf(metric); + const currentRows = await scannedRows({ metric, ...current, groupBy }); + const previousRows = await scannedRows({ metric, ...previous, groupBy }); + const currentTotal = total(currentRows, unit); + const previousTotal = total(previousRows, unit); + + return { + metric, + current: { ...current, total: currentTotal }, + previous: { ...previous, total: previousTotal }, + change: changeOf(currentTotal, previousTotal), + series: { + current: dailySeries(currentRows, unit).map((r) => r.value), + previous: dailySeries(previousRows, unit).map((r) => r.value), + }, + groups: groupBy + ? totalsByGroup(currentRows, previousRows, groupBy, unit) + : undefined, + }; + }, + }), + + top_movers: tool({ + description: + "Split a date range in half and rank one dimension's values by how " + + "much they moved from the first half to the second half, largest " + + "absolute change first.", + inputSchema: z.object({ + metric: z.string().describe("A metric name from list_metrics"), + dimension: z + .string() + .describe("One of the metric's dimensions, from list_metrics"), + from: z.string().describe("Inclusive start date, YYYY-MM-DD"), + to: z.string().describe("Inclusive end date, YYYY-MM-DD"), + }), + execute: async ({ metric, dimension, from, to }) => { + const unit = unitOf(metric); + const { midBefore, midAfter } = splitRange(from, to); + const beforeRows = await scannedRows({ + metric, + from, + to: midBefore, + groupBy: dimension, + }); + const afterRows = await scannedRows({ + metric, + from: midAfter, + to, + groupBy: dimension, + }); + + const values = new Set([ + ...beforeRows.map((r) => String(r[dimension])), + ...afterRows.map((r) => String(r[dimension])), + ]); + + const movers = [...values] + .map((value) => { + const before = total( + beforeRows.filter((r) => r[dimension] === value), + unit, + ); + const after = total( + afterRows.filter((r) => r[dimension] === value), + unit, + ); + return { [dimension]: value, before, after, change: changeOf(after, before) }; + }) + .sort((a, b) => Math.abs(b.change.absolute) - Math.abs(a.change.absolute)); + + return { metric, dimension, from, to, movers }; + }, + }), +}; diff --git a/apps/slack-agent/src/app.ts b/apps/slack-agent/src/app.ts index e8e0b53..ac6b07d 100644 --- a/apps/slack-agent/src/app.ts +++ b/apps/slack-agent/src/app.ts @@ -1,25 +1,16 @@ +import "./instrument.ts"; +import * as Sentry from "@sentry/node"; import { App } from "@slack/bolt"; import type { SayFn, SayStreamFn, SetStatusFn } from "@slack/bolt"; import type { BlockFeedbackButtonsAction } from "@slack/bolt"; -import type { BlockButtonAction } from "@slack/bolt"; -import type { - AppMentionEvent, - CarouselBlock, - KnownBlock, - MessageEvent, - TableBlock, -} from "@slack/types"; +import type { AppMentionEvent, KnownBlock, MessageEvent } from "@slack/types"; import type { WebClient } from "@slack/web-api"; import type { ModelMessage } from "ai"; -import type { AccountInfo, ProductCard } from "lib/ai/tools"; -import { streamAnswer, TOOL_TITLES } from "./agent"; +import { streamAnswer } from "./agent.ts"; +import { answerBlocks, type ToolOutcome } from "./analytics/blocks.ts"; +import { TOOL_TITLES } from "./analytics/tools.ts"; -const required = [ - "SLACK_BOT_TOKEN", - "SLACK_APP_TOKEN", - "OPENROUTER_API_KEY", - "DATABASE_URL", -] as const; +const required = ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN", "OPENROUTER_API_KEY"] as const; for (const name of required) { if (!process.env[name]) { @@ -30,7 +21,7 @@ for (const name of required) { } } -const ERROR_REPLY = "The shopping assistant hit an error. Please try again."; +const ERROR_REPLY = "The analytics assistant hit an error. Please try again."; const FEEDBACK_BLOCK: KnownBlock[] = [ { @@ -38,7 +29,7 @@ const FEEDBACK_BLOCK: KnownBlock[] = [ elements: [ { type: "mrkdwn", - text: "AI-generated. Check order details before you act on them.", + text: "AI-generated. Verify important numbers before you act on them.", }, ], }, @@ -61,12 +52,12 @@ const FEEDBACK_BLOCK: KnownBlock[] = [ }, ]; -// Shopping-flavored status lines shown while the assistant is thinking. +// Analytics-flavored status lines shown while the assistant is thinking. const LOADING_MESSAGES = [ - "Checking the shelves...", - "Reading the order book...", - "Counting loyalty points...", - "Fetching product details...", + "Querying the warehouse...", + "Crunching the numbers...", + "Checking the date range...", + "Comparing periods...", ]; const app = new App({ @@ -79,6 +70,18 @@ const app = new App({ // call of its own thread. const activeRuns = new Map(); +// Bolt handles every event inside one long-lived process. Without a fresh +// isolation scope and trace, each message would join the previous message's +// trace and inherit its user and conversation id. +function startMessageSpan( + options: Parameters[0], + callback: () => Promise, +): Promise { + return Sentry.withIsolationScope(() => + Sentry.startNewTrace(() => Sentry.startSpan(options, callback)), + ); +} + async function respond({ client, event, @@ -86,6 +89,7 @@ async function respond({ sayStream, setStatus, say, + channelType, }: { client: WebClient; event: { channel: string; ts: string; thread_ts?: string; user?: string }; @@ -93,109 +97,127 @@ async function respond({ sayStream: SayStreamFn; setStatus: SetStatusFn; say: SayFn; + channelType: "im" | "channel"; }) { const conversationId = event.thread_ts ?? event.ts; + const recordContent = channelType !== "im"; let stream: ReturnType | undefined; const run = new AbortController(); const runKey = `${event.channel}:${conversationId}`; activeRuns.set(runKey, run); - try { - await setStatus({ - status: "is thinking...", - loading_messages: LOADING_MESSAGES, - }).catch((error) => console.warn("setStatus failed", error)); - - const messages = await loadThread({ client, event, conversationId, text }); - - stream = sayStream({ task_display_mode: "timeline" }); - - const taskStatus = { - "tool-call": "in_progress", - "tool-result": "complete", - "tool-error": "error", - } as const; - - let reply = ""; - const found = new Map(); - let orders: AccountInfo["orders"] = []; - const failed: string[] = []; - - for await (const part of streamAnswer({ - messages, - conversationId, - abortSignal: run.signal, - }).fullStream) { - switch (part.type) { - case "text-delta": - reply += part.text; - await stream.append({ markdown_text: part.text }); - break; - case "tool-call": - case "tool-result": - case "tool-error": - await stream.append({ - chunks: [ - { - type: "task_update", - id: part.toolCallId, - title: TOOL_TITLES[part.toolName] ?? part.toolName, - status: taskStatus[part.type], - }, - ], - }); - if (part.type === "tool-result" && !part.dynamic) { - if (part.toolName === "searchProducts") { - for (const p of part.output.products) found.set(p.handle, p); - } else if (part.toolName === "getProduct" && part.output.product) { - found.set(part.output.product.handle, part.output.product); - } else if (part.toolName === "getAccountInfo") { - orders = part.output.orders; - } - } - if (part.type === "tool-error") { - failed.push(TOOL_TITLES[part.toolName] ?? part.toolName); + // One Slack message is one trace: this span is the root, so the model and + // tool spans from the AI SDK's telemetry nest inside it. + await startMessageSpan( + { + name: "slack.message", + op: "slack.message", + attributes: { + "gen_ai.conversation.id": conversationId, + "slack.channel": event.channel, + "slack.channel_type": channelType, + "slack.record_content": recordContent, + }, + }, + async () => { + // The span has its own scope, so threads that run at the same time keep + // their own user and conversation. Sentry copies the conversation id + // from the scope to each AI span only, and groups the thread by it. + const scope = Sentry.getCurrentScope(); + scope.setUser(event.user ? { id: event.user } : null); + scope.setConversationId(conversationId); + + try { + await setStatus({ + status: "is thinking...", + loading_messages: LOADING_MESSAGES, + }).catch((error) => console.warn("setStatus failed", error)); + + const messages = await loadThread({ + client, + event, + conversationId, + text, + }); + + stream = sayStream({ task_display_mode: "timeline" }); + + const taskStatus = { + "tool-call": "in_progress", + "tool-result": "complete", + "tool-error": "error", + } as const; + + const failed: string[] = []; + const outcomes: ToolOutcome[] = []; + + for await (const part of streamAnswer({ + messages, + conversationId, + abortSignal: run.signal, + recordContent, + }).fullStream) { + switch (part.type) { + case "text-delta": + await stream.append({ markdown_text: part.text }); + break; + case "tool-call": + case "tool-result": + case "tool-error": + await stream.append({ + chunks: [ + { + type: "task_update", + id: part.toolCallId, + title: TOOL_TITLES[part.toolName] ?? part.toolName, + status: taskStatus[part.type], + }, + ], + }); + if (part.type === "tool-result") { + outcomes.push({ + toolName: part.toolName, + input: part.input, + output: part.output, + }); + } + if (part.type === "tool-error") { + failed.push(TOOL_TITLES[part.toolName] ?? part.toolName); + } + break; + case "abort": + // The user pressed stop. Slack has already closed the streamed + // message, so there is nothing left to stop or decorate. + return; + case "error": + throw part.error; + default: + break; } - break; - case "abort": - // The user pressed stop. Slack has already closed the streamed - // message, so there is nothing left to stop or decorate. - return; - case "error": - throw part.error; - default: - break; + } + + await stream.stop({ + blocks: [...failureNotice(failed), ...answerBlocks(outcomes), ...FEEDBACK_BLOCK], + }); + } catch (error) { + Sentry.captureException(error); + if (stream) { + await stream.stop({ markdown_text: ERROR_REPLY }); + } else { + await say({ text: ERROR_REPLY, thread_ts: conversationId }); + } + throw error; + } finally { + activeRuns.delete(runKey); + // Slack's agent messaging experience keeps the status until the app + // clears it; the older assistant experience cleared it on the reply. + await setStatus("").catch((error) => + console.warn("setStatus failed", error), + ); } - } - - // A search returns up to six products and the model often picks a few - // (for example "under $60"), so show only the ones the reply names. - const products = [...found.values()].filter((p) => reply.includes(p.title)); - const named = orders.filter((o) => reply.includes(o.id)); - await stream.stop({ - blocks: [ - ...failureNotice(failed), - ...productCarousel(products), - ...ordersTable(named), - ...FEEDBACK_BLOCK, - ], - }); - } catch (error) { - if (stream) { - await stream.stop({ markdown_text: ERROR_REPLY }); - } else { - await say({ text: ERROR_REPLY, thread_ts: conversationId }); - } - throw error; - } finally { - activeRuns.delete(runKey); - // Slack's agent messaging experience keeps the status until the app - // clears it; the older assistant experience cleared it on the reply. - await setStatus("").catch((error) => - console.warn("setStatus failed", error), - ); - } + }, + ); } app.event( @@ -214,8 +236,17 @@ app.event( client: WebClient; }) => { const text = stripMention(event.text ?? ""); - await titleThread(client, event, text); - await respond({ client, event, text, sayStream, setStatus, say }); + // Slack sends no channel_type with app_mention, so a mention in a + // private channel counts as a channel here too. + await respond({ + client, + event, + text, + sayStream, + setStatus, + say, + channelType: "channel", + }); }, ); @@ -243,7 +274,15 @@ app.event( const text = stripMention(event.text ?? ""); await titleThread(client, event, text); - await respond({ client, event, text, sayStream, setStatus, say }); + await respond({ + client, + event, + text, + sayStream, + setStatus, + say, + channelType: "im", + }); }, ); @@ -257,42 +296,6 @@ app.event("agent_session_stopped", async ({ event }) => { activeRuns.get(`${channel}:${thread_ts}`)?.abort(); }); -// A click on a product card's button is a new turn in the same thread. -app.action( - "product_details", - async ({ ack, action, body, client, context, say }) => { - await ack(); - const channel = body.channel?.id; - const thread_ts = body.message?.thread_ts ?? body.message?.ts; - if (!channel || !thread_ts || !action.value) return; - - // Bolt gives sayStream and setStatus to event listeners only. - const sayStream: SayStreamFn = (args) => - client.chatStream({ - channel, - thread_ts, - recipient_team_id: context.teamId ?? context.enterpriseId, - recipient_user_id: body.user.id, - ...args, - }); - const setStatus: SetStatusFn = (status) => - client.assistant.threads.setStatus({ - channel_id: channel, - thread_ts, - ...(typeof status === "string" ? { status } : status), - }); - - await respond({ - client, - event: { channel, ts: action.action_ts, thread_ts, user: body.user.id }, - text: `Tell me more about the ${action.value}.`, - sayStream, - setStatus, - say, - }); - }, -); - app.action( "feedback", async ({ ack, payload, body, client }) => { @@ -310,59 +313,8 @@ app.action( }, ); -// Slack loads card images over the public internet, and the storefront runs -// only on localhost, so the cards read the same files from GitHub. -const PRODUCT_IMAGE_BASE = - "https://raw.githubusercontent.com/getsentry/debugging-agents-workshop/main/apps/storefront/public"; - -function productCarousel(products: ProductCard[]): CarouselBlock[] { - if (products.length === 0) return []; - return [ - { - type: "carousel", - // Slack limits: 10 cards per carousel, 200 characters per card body. - elements: products.slice(0, 10).map((p) => ({ - type: "card", - hero_image: { - type: "image", - image_url: `${PRODUCT_IMAGE_BASE}${p.image}`, - alt_text: p.title, - }, - title: { type: "mrkdwn", text: p.title }, - subtitle: { type: "mrkdwn", text: `$${p.price}` }, - body: { type: "mrkdwn", text: p.description.slice(0, 200) }, - actions: [ - { - type: "button", - action_id: "product_details", - text: { type: "plain_text", text: "Tell me more" }, - value: p.title, - }, - ], - })), - }, - ]; -} - -function ordersTable(orders: AccountInfo["orders"]): TableBlock[] { - if (orders.length === 0) return []; - const row = (...cells: string[]) => - cells.map((text) => ({ type: "raw_text" as const, text })); - return [ - { - type: "table", - column_settings: [{}, {}, { align: "right" }], - rows: [ - row("Order", "Status", "Total"), - ...orders.map((o) => row(o.id, o.status, `$${o.total}`)), - ], - }, - ]; -} - -// Slack accepts its red "alert" block only in modals, so a failed tool gets a -// plain section. The model may word a failure softly; this line comes from -// the tool error itself. +// A failed tool call gets a plain section. The model may word a failure +// softly; this line comes from the tool error itself. function failureNotice(failed: string[]): KnownBlock[] { if (failed.length === 0) return []; return [ @@ -376,7 +328,8 @@ function failureNotice(failed: string[]): KnownBlock[] { ]; } -// The title is the label of the thread in Slack's list of agent chats. +// The title is the label of the thread in Slack's list of agent chats. Slack +// accepts it only for DM threads, so the mention handler does not call this. async function titleThread( client: WebClient, event: { channel: string; ts: string; thread_ts?: string }, @@ -388,7 +341,7 @@ async function titleThread( .catch((error) => console.warn("setTitle failed", error)); } -// Mentions arrive as "<@U0123> show me the apparel collection" - strip the +// Mentions arrive as "<@U0123> how did signups do last week" - strip the // leading mention so the model sees a plain question. function stripMention(text: string): string { return text.replace(/^\s*<@[^>]+>\s*/, "").trim(); @@ -396,8 +349,8 @@ function stripMention(text: string): string { // Gives the agent thread memory: without a thread_ts the event is the start // of a new thread, so its own text is the whole history. With a thread_ts, -// replay the thread from Slack so follow-ups like "refund that order" carry -// the context a fresh call to streamAnswer() would otherwise lose. +// replay the thread from Slack so follow-ups like "and by device?" carry the +// context a fresh call to streamAnswer() would otherwise lose. async function loadThread({ client, event, @@ -435,8 +388,8 @@ async function loadThread({ } // A bot message's `text` also holds Slack's plain-text fallback for the task -// timeline, the cards, and the footer. The model copies that pattern into new -// replies when it sees it in the history, so replay only the streamed text. +// timeline and the footer. The model copies that pattern into new replies +// when it sees it in the history, so replay only the streamed text. function replyText(message: { text?: string; blocks?: unknown[] }): string { const flatten = (node: unknown): string => { if (typeof node !== "object" || node === null) return ""; diff --git a/apps/slack-agent/src/instrument.ts b/apps/slack-agent/src/instrument.ts new file mode 100644 index 0000000..a35ca84 --- /dev/null +++ b/apps/slack-agent/src/instrument.ts @@ -0,0 +1,11 @@ +import * as Sentry from "@sentry/node"; + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: process.env.SENTRY_RELEASE, + environment: process.env.SENTRY_ENVIRONMENT ?? "development", + tracesSampleRate: 1, + // Prompts and replies are off by default for this app; src/agent.ts turns + // them on per call when the message is in a channel. + dataCollection: { genAI: { inputs: false, outputs: false } }, +}); diff --git a/apps/slack-agent/tsconfig.json b/apps/slack-agent/tsconfig.json index b2c5b66..4882247 100644 --- a/apps/slack-agent/tsconfig.json +++ b/apps/slack-agent/tsconfig.json @@ -11,9 +11,7 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "paths": { - "lib/*": ["../storefront/lib/*"] - } + "allowImportingTsExtensions": true }, "include": ["src/**/*.ts"] } diff --git a/apps/storefront/.env.example b/apps/storefront/.env.example index 9ea2eea..0699bf1 100644 --- a/apps/storefront/.env.example +++ b/apps/storefront/.env.example @@ -14,4 +14,12 @@ OPENROUTER_MODEL= COMPANY_NAME="Acme, Inc." SITE_NAME="Acme Store" -# Sentry variables are added in labs/02-instrument-storefront.md. +# Sentry DSN for the server runtime (Project Settings > Client Keys) +SENTRY_DSN= +# Same DSN, public var so the browser SDK sees it too +NEXT_PUBLIC_SENTRY_DSN= +# Sentry org and project slugs — used by the build for source map upload +SENTRY_ORG= +SENTRY_PROJECT= +# Optional auth token; when unset, source map upload is skipped and builds still pass +SENTRY_AUTH_TOKEN= diff --git a/apps/storefront/app/api/chat/route.ts b/apps/storefront/app/api/chat/route.ts index d95d031..d689062 100644 --- a/apps/storefront/app/api/chat/route.ts +++ b/apps/storefront/app/api/chat/route.ts @@ -1,4 +1,5 @@ import { createOpenRouter } from "@openrouter/ai-sdk-provider"; +import * as Sentry from "@sentry/nextjs"; import { convertToModelMessages, createUIMessageStreamResponse, @@ -32,6 +33,14 @@ export async function POST(req: Request) { const { id, messages }: { id?: string; messages: AssistantUIMessage[] } = await req.json(); + // Attribute this turn to the shopper and thread all turns of one chat + // session into a single Sentry Conversation. Must happen before the AI call + // so the gen_ai spans pick both up. + Sentry.setUser({ ...DEMO_USER }); + if (id) { + Sentry.setConversationId(id); + } + const result = streamText({ model: openrouter.chat(resolveModel()), instructions: { @@ -46,6 +55,11 @@ export async function POST(req: Request) { telemetry: { functionId: "shopping-assistant", }, + // streamText resolves rather than throws when the model or the provider + // fails, so without this a bad key or a provider 5xx creates no issue. + onError: ({ error }) => { + Sentry.captureException(error); + }, }); return createUIMessageStreamResponse({ diff --git a/apps/storefront/components/assistant/account-card.tsx b/apps/storefront/components/assistant/account-card.tsx index 1ecb103..f1fcadf 100644 --- a/apps/storefront/components/assistant/account-card.tsx +++ b/apps/storefront/components/assistant/account-card.tsx @@ -24,9 +24,12 @@ export function AccountCard({ customer, orders }: AccountInfo) {
-

{customer.name}

+

+ {customer.name} +

- {customer.email} · member since {customer.memberSince} + {customer.email} · member since{" "} + {customer.memberSince}

diff --git a/apps/storefront/instrumentation-client.ts b/apps/storefront/instrumentation-client.ts new file mode 100644 index 0000000..9680b7a --- /dev/null +++ b/apps/storefront/instrumentation-client.ts @@ -0,0 +1,23 @@ +import * as Sentry from "@sentry/nextjs"; +import { DEMO_USER } from "lib/demo-user"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1, + replaysSessionSampleRate: 1, + integrations: [ + // Records the page as the shopper saw it. Elements marked + // data-sentry-mask (the account card's name and email) stay masked. + Sentry.replayIntegration({ + maskAllText: false, + maskAllInputs: false, + blockAllMedia: false, + }), + ], +}); + +// The store has no sign-in; a real app calls setUser after the user signs in. +Sentry.setUser({ ...DEMO_USER }); + +// Instruments App Router navigations so client traces connect to server ones. +export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/apps/storefront/instrumentation.ts b/apps/storefront/instrumentation.ts new file mode 100644 index 0000000..77e3775 --- /dev/null +++ b/apps/storefront/instrumentation.ts @@ -0,0 +1,10 @@ +import * as Sentry from "@sentry/nextjs"; + +export async function register() { + if (process.env.NEXT_RUNTIME === "nodejs") { + await import("./sentry.server.config"); + } +} + +// Capture errors from Server Components, middleware, and proxies. +export const onRequestError = Sentry.captureRequestError; diff --git a/apps/storefront/next.config.ts b/apps/storefront/next.config.ts index 1d8a08a..4b32a9b 100644 --- a/apps/storefront/next.config.ts +++ b/apps/storefront/next.config.ts @@ -1,3 +1,4 @@ +import { withSentryConfig } from "@sentry/nextjs/config"; import type { NextConfig } from "next"; const nextConfig: NextConfig = { @@ -16,4 +17,9 @@ const nextConfig: NextConfig = { }, }; -export default nextConfig; +export default withSentryConfig(nextConfig, { + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + silent: !process.env.CI, +}); diff --git a/apps/storefront/package-lock.json b/apps/storefront/package-lock.json index fd84098..af5d9ab 100644 --- a/apps/storefront/package-lock.json +++ b/apps/storefront/package-lock.json @@ -9,6 +9,7 @@ "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", "@openrouter/ai-sdk-provider": "3.0.0", + "@sentry/nextjs": "11.0.0", "@streamdown/cjk": "^1.0.3", "@streamdown/code": "^1.1.1", "@streamdown/math": "^1.0.2", @@ -166,6 +167,230 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://sfw.security.sentry.io/npm/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://sfw.security.sentry.io/npm/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://sfw.security.sentry.io/npm/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://sfw.security.sentry.io/npm/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -187,7 +412,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@emnapi/runtime/-/runtime-1.11.3.tgz", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", "optional": true, @@ -1206,7 +1431,7 @@ }, "node_modules/@img/sharp-darwin-x64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", "cpu": [ "x64" @@ -1228,7 +1453,7 @@ }, "node_modules/@img/sharp-freebsd-wasm32": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", "license": "Apache-2.0", "optional": true, @@ -1263,7 +1488,7 @@ }, "node_modules/@img/sharp-libvips-darwin-x64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", "cpu": [ "x64" @@ -1279,7 +1504,7 @@ }, "node_modules/@img/sharp-libvips-linux-arm": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", "cpu": [ "arm" @@ -1298,7 +1523,7 @@ }, "node_modules/@img/sharp-libvips-linux-arm64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", "cpu": [ "arm64" @@ -1317,7 +1542,7 @@ }, "node_modules/@img/sharp-libvips-linux-ppc64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", "cpu": [ "ppc64" @@ -1336,7 +1561,7 @@ }, "node_modules/@img/sharp-libvips-linux-riscv64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", "cpu": [ "riscv64" @@ -1355,7 +1580,7 @@ }, "node_modules/@img/sharp-libvips-linux-s390x": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", "cpu": [ "s390x" @@ -1374,7 +1599,7 @@ }, "node_modules/@img/sharp-libvips-linux-x64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", "cpu": [ "x64" @@ -1393,7 +1618,7 @@ }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", "cpu": [ "arm64" @@ -1412,7 +1637,7 @@ }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", "cpu": [ "x64" @@ -1431,7 +1656,7 @@ }, "node_modules/@img/sharp-linux-arm": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", "cpu": [ "arm" @@ -1456,7 +1681,7 @@ }, "node_modules/@img/sharp-linux-arm64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", "cpu": [ "arm64" @@ -1481,7 +1706,7 @@ }, "node_modules/@img/sharp-linux-ppc64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", "cpu": [ "ppc64" @@ -1506,7 +1731,7 @@ }, "node_modules/@img/sharp-linux-riscv64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", "cpu": [ "riscv64" @@ -1531,7 +1756,7 @@ }, "node_modules/@img/sharp-linux-s390x": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", "cpu": [ "s390x" @@ -1556,7 +1781,7 @@ }, "node_modules/@img/sharp-linux-x64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", "cpu": [ "x64" @@ -1581,7 +1806,7 @@ }, "node_modules/@img/sharp-linuxmusl-arm64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", "cpu": [ "arm64" @@ -1606,7 +1831,7 @@ }, "node_modules/@img/sharp-linuxmusl-x64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", "cpu": [ "x64" @@ -1631,7 +1856,7 @@ }, "node_modules/@img/sharp-wasm32": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, @@ -1647,7 +1872,7 @@ }, "node_modules/@img/sharp-webcontainers-wasm32": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", "cpu": [ "wasm32" @@ -1666,7 +1891,7 @@ }, "node_modules/@img/sharp-win32-arm64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", "cpu": [ "arm64" @@ -1685,7 +1910,7 @@ }, "node_modules/@img/sharp-win32-ia32": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", "cpu": [ "ia32" @@ -1704,7 +1929,7 @@ }, "node_modules/@img/sharp-win32-x64": { "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", "cpu": [ "x64" @@ -1752,7 +1977,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1763,7 +1987,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1774,7 +1997,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1784,14 +2006,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1807,6 +2027,25 @@ "@chevrotain/types": "~11.1.2" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://sfw.security.sentry.io/npm/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@next/env": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.0.tgz", @@ -1831,7 +2070,7 @@ }, "node_modules/@next/swc-darwin-x64": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.0.tgz", "integrity": "sha512-SOi96kSaF5T+0wW4koiM1bWzSPwjzTesC1p3df+FjdOi5LIQkBK/blxh7HdoKnNuI4PURF1OO7TZqtfnbWDSgw==", "cpu": [ "x64" @@ -1847,7 +2086,7 @@ }, "node_modules/@next/swc-linux-arm64-gnu": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.0.tgz", "integrity": "sha512-P0gZAoPMF4dyTRzhmkV4PrqVzSOB6t4mC1oI3c4dqijJ+OVEVx5clIXAKR4/uQpsqw2KKM/0D5tVumcR2r5blg==", "cpu": [ "arm64" @@ -1866,7 +2105,7 @@ }, "node_modules/@next/swc-linux-arm64-musl": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.0.tgz", "integrity": "sha512-tXXGKJw0m37O0eKJARVTX/TheKPhz0QFVtVVZXmOig+9YKLQOSP6hvf2pxv5DO7CLEJyTHx3Pg043CDQkv1G4Q==", "cpu": [ "arm64" @@ -1885,7 +2124,7 @@ }, "node_modules/@next/swc-linux-x64-gnu": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.0.tgz", "integrity": "sha512-pjGxK5EY7yWml78ALejFkWmgHsU7wbFQrISiugpH6FbUJhgEvw3xFZ/EBAtLl7QtL0WdQKiG9eWJ3mOKGTukHw==", "cpu": [ "x64" @@ -1904,7 +2143,7 @@ }, "node_modules/@next/swc-linux-x64-musl": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.0.tgz", "integrity": "sha512-sjo++Xx+lomlPs3HRsHWhVDyGG6ms1kGW5EtHLERdII8AyG1i+f6aq68xHREO6AEMlhjTNEWBSmfJfqm9orf7g==", "cpu": [ "x64" @@ -1923,7 +2162,7 @@ }, "node_modules/@next/swc-win32-arm64-msvc": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.0.tgz", "integrity": "sha512-C5JSgiO54wURdaxdEUIXqkz04uMqC9UmPX1gtDrV/5Tf1UowdWYI8uA5hfFbPolTlp0q4KZ60xlHePNibf0VIw==", "cpu": [ "arm64" @@ -1939,7 +2178,7 @@ }, "node_modules/@next/swc-win32-x64-msvc": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.0.tgz", "integrity": "sha512-fDOggsweNb5SSw0ZKVk6U+gxSyGFFlIBY/LBc1r8GUj4u/6t6oArL+Pmkg0MBnsgR+KkdsURilVH4F3GXUGepA==", "cpu": [ "x64" @@ -1966,14 +2205,22 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.78.0.tgz", - "integrity": "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw==", + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://sfw.security.sentry.io/npm/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.143.0.tgz", + "integrity": "sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1983,14 +2230,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-android-arm64": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-android-arm64/-/binding-android-arm64-1.78.0.tgz", - "integrity": "sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ==", + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.143.0.tgz", + "integrity": "sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2000,14 +2246,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.78.0.tgz", - "integrity": "sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA==", + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.143.0.tgz", + "integrity": "sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2017,14 +2262,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.78.0.tgz", - "integrity": "sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g==", + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.143.0.tgz", + "integrity": "sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2034,14 +2278,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.78.0.tgz", - "integrity": "sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.143.0.tgz", + "integrity": "sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2051,14 +2294,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.78.0.tgz", - "integrity": "sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.143.0.tgz", + "integrity": "sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2068,14 +2310,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.78.0.tgz", - "integrity": "sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ==", + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.143.0.tgz", + "integrity": "sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2085,14 +2326,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.78.0.tgz", - "integrity": "sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg==", + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.143.0.tgz", + "integrity": "sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==", "cpu": [ "arm64" ], - "dev": true, "libc": [ "glibc" ], @@ -2105,14 +2345,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.78.0.tgz", - "integrity": "sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg==", + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.143.0.tgz", + "integrity": "sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==", "cpu": [ "arm64" ], - "dev": true, "libc": [ "musl" ], @@ -2125,14 +2364,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.78.0.tgz", - "integrity": "sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ==", + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.143.0.tgz", + "integrity": "sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==", "cpu": [ "ppc64" ], - "dev": true, "libc": [ "glibc" ], @@ -2145,14 +2383,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.78.0.tgz", - "integrity": "sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA==", + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.143.0.tgz", + "integrity": "sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==", "cpu": [ "riscv64" ], - "dev": true, "libc": [ "glibc" ], @@ -2165,14 +2402,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.78.0.tgz", - "integrity": "sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg==", + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.143.0.tgz", + "integrity": "sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==", "cpu": [ "riscv64" ], - "dev": true, "libc": [ "musl" ], @@ -2185,14 +2421,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.78.0.tgz", - "integrity": "sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw==", + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.143.0.tgz", + "integrity": "sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==", "cpu": [ "s390x" ], - "dev": true, "libc": [ "glibc" ], @@ -2205,14 +2440,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.78.0.tgz", - "integrity": "sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ==", + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.143.0.tgz", + "integrity": "sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==", "cpu": [ "x64" ], - "dev": true, "libc": [ "glibc" ], @@ -2225,14 +2459,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.78.0.tgz", - "integrity": "sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg==", + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.143.0.tgz", + "integrity": "sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==", "cpu": [ "x64" ], - "dev": true, "libc": [ "musl" ], @@ -2245,14 +2478,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.78.0.tgz", - "integrity": "sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg==", + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.143.0.tgz", + "integrity": "sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2262,14 +2494,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.78.0.tgz", - "integrity": "sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA==", + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.143.0.tgz", + "integrity": "sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2279,14 +2510,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.78.0.tgz", - "integrity": "sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA==", + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.143.0.tgz", + "integrity": "sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2296,14 +2526,13 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.78.0", - "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.78.0.tgz", - "integrity": "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA==", + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.143.0.tgz", + "integrity": "sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2313,447 +2542,381 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/number": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", - "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", - "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accessible-icon": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz", - "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==", + "node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", "license": "MIT", - "dependencies": { - "@radix-ui/react-visually-hidden": "1.2.11" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", - "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collapsible": "1.1.20", - "@radix-ui/react-collection": "1.1.15", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", - "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.78.0.tgz", + "integrity": "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-dialog": "1.1.23", - "@radix-ui/react-primitive": "2.1.10" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", - "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-android-arm64/-/binding-android-arm64-1.78.0.tgz", + "integrity": "sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.10" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz", - "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==", + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.78.0.tgz", + "integrity": "sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.10" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-avatar": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", - "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.78.0.tgz", + "integrity": "sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-is-hydrated": "0.1.3", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", - "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.78.0.tgz", + "integrity": "sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-size": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", - "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.78.0.tgz", + "integrity": "sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", - "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-slot": "1.3.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.78.0.tgz", + "integrity": "sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", - "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.78.0.tgz", + "integrity": "sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-context": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", - "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.78.0.tgz", + "integrity": "sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-context-menu": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz", - "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==", + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.78.0.tgz", + "integrity": "sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-menu": "2.1.24", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", - "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.78.0.tgz", + "integrity": "sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-dismissable-layer": "1.1.19", - "@radix-ui/react-focus-guards": "1.1.6", - "@radix-ui/react-focus-scope": "1.1.16", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-portal": "1.1.17", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-slot": "1.3.3", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", - "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.78.0.tgz", + "integrity": "sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", - "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.78.0.tgz", + "integrity": "sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-effect-event": "0.0.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", - "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-menu": "2.1.24", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.78.0.tgz", + "integrity": "sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", - "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.78.0.tgz", + "integrity": "sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", - "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.78.0.tgz", + "integrity": "sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.78.0.tgz", + "integrity": "sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.78.0.tgz", + "integrity": "sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.78.0", + "resolved": "https://sfw.security.sentry.io/npm/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.78.0.tgz", + "integrity": "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz", + "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4" + "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", @@ -2770,18 +2933,21 @@ } } }, - "node_modules/@radix-ui/react-form": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz", - "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==", + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", + "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-label": "2.1.15", - "@radix-ui/react-primitive": "2.1.10" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -2798,21 +2964,17 @@ } } }, - "node_modules/@radix-ui/react-hover-card": { + "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.23", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", - "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-dismissable-layer": "1.1.19", - "@radix-ui/react-popper": "1.3.7", - "@radix-ui/react-portal": "1.1.17", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6" + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -2829,28 +2991,33 @@ } } }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", - "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", - "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz", + "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.10" @@ -2870,30 +3037,18 @@ } } }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", - "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", - "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.19", - "@radix-ui/react-focus-guards": "1.1.6", - "@radix-ui/react-focus-scope": "1.1.16", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-popper": "1.3.7", - "@radix-ui/react-portal": "1.1.17", - "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.2" + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2910,22 +3065,19 @@ } } }, - "node_modules/@radix-ui/react-menubar": { - "version": "1.1.24", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", - "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-use-controllable-state": "1.2.6" + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2942,26 +3094,20 @@ } } }, - "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.22", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", - "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4", - "@radix-ui/react-use-previous": "1.1.4", - "@radix-ui/react-visually-hidden": "1.2.11" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2978,24 +3124,16 @@ } } }, - "node_modules/@radix-ui/react-one-time-password-field": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz", - "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==", + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.3", - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-effect-event": "0.0.5", - "@radix-ui/react-use-is-hydrated": "0.1.3", - "@radix-ui/react-use-layout-effect": "1.1.4" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -3012,20 +3150,47 @@ } } }, - "node_modules/@radix-ui/react-password-toggle-field": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz", - "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==", + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz", + "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-effect-event": "0.0.5", - "@radix-ui/react-use-is-hydrated": "0.1.3" + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -3042,10 +3207,10 @@ } } }, - "node_modules/@radix-ui/react-popover": { + "node_modules/@radix-ui/react-dialog": { "version": "1.1.23", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", - "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", @@ -3055,12 +3220,12 @@ "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -3079,22 +3244,32 @@ } } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", - "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-layout-effect": "1.1.4", - "@radix-ui/react-use-rect": "1.1.4", - "@radix-ui/react-use-size": "1.1.4", - "@radix-ui/rect": "1.1.3" + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", @@ -3111,14 +3286,19 @@ } } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", - "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", "license": "MIT", "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-layout-effect": "1.1.4" + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -3135,36 +3315,30 @@ } } }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", - "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" - }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", - "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.3.3" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3181,13 +3355,17 @@ } } }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", - "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", + "node_modules/@radix-ui/react-form": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz", + "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==", "license": "MIT", "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-label": "2.1.15", "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { @@ -3205,21 +3383,21 @@ } } }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", - "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", + "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-size": "1.1.4" + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -3236,54 +3414,31 @@ } } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", - "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", - "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.3", - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-layout-effect": "1.1.4" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -3300,13 +3455,12 @@ } } }, - "node_modules/@radix-ui/react-select": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", - "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", @@ -3320,12 +3474,9 @@ "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4", - "@radix-ui/react-use-previous": "1.1.4", - "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -3344,13 +3495,22 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", - "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.10" + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", + "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -3367,23 +3527,26 @@ } } }, - "node_modules/@radix-ui/react-slider": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", - "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", + "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", - "@radix-ui/react-use-size": "1.1.4" + "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", @@ -3400,36 +3563,54 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", - "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz", + "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.5" + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-switch": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", - "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz", + "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-size": "1.1.4" + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-is-hydrated": "0.1.3" }, "peerDependencies": { "@types/react": "*", @@ -3446,20 +3627,27 @@ } } }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", - "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "node_modules/@radix-ui/react-popover": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-use-controllable-state": "1.2.6" + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", @@ -3476,24 +3664,147 @@ } } }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.23", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz", - "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==", + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-dismissable-layer": "1.1.19", - "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4", - "@radix-ui/react-visually-hidden": "1.2.11" + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3510,15 +3821,23 @@ } } }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", - "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-controllable-state": "1.2.6" + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -3535,316 +3854,1241 @@ } } }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", - "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz", + "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", + "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", + "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz", + "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-toggle-group": "1.1.19" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz", + "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@react-aria/focus": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz", + "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz", + "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz", + "integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.1", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", + "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz", + "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz", + "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz", + "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz", + "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz", + "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz", + "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz", + "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz", + "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz", + "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz", + "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz", + "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz", + "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz", + "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz", + "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz", + "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz", + "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz", + "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz", + "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz", + "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz", + "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz", + "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz", + "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz", + "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz", + "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz", + "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sentry/browser": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/browser/-/browser-11.0.0.tgz", + "integrity": "sha512-a09jUpx4OBebmY0XLYXLhfvkCIken8xoncjmOmbPIa3ibUHI6ZJVyh3jAzMu56LY9xfJRV5bOfp92yf41Yr6dQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-toggle": "1.1.18", - "@radix-ui/react-use-controllable-state": "1.2.6" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@sentry/browser-utils": "11.0.0", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0", + "@sentry/feedback": "11.0.0", + "@sentry/replay": "11.0.0", + "@sentry/replay-canvas": "11.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/react-toolbar": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz", - "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==", + "node_modules/@sentry/browser-utils": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/browser-utils/-/browser-utils-11.0.0.tgz", + "integrity": "sha512-ZvGyfDoVeSuaAF78vSeYOFcibUCpQfYlUVpqE+B0RANzoSCBg3JimRa+cDYB/rq36PjRwINk4u2viK2JdO320w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-separator": "1.1.15", - "@radix-ui/react-toggle-group": "1.1.19" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0", + "web-vitals": "^6.1.1" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", - "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "node_modules/@sentry/bundler-plugins": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/bundler-plugins/-/bundler-plugins-11.0.0.tgz", + "integrity": "sha512-Oe4/8CbsYrqdjTt91JFMBQza59IWUfSwPrlHGbbJT+qVCyebX2bykxv3mfa6Ty/Gmt8+gva/DI6Kjcyb9EgXBg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-dismissable-layer": "1.1.19", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-popper": "1.3.7", - "@radix-ui/react-portal": "1.1.17", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-slot": "1.3.3", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4", - "@radix-ui/react-visually-hidden": "1.2.11" + "@babel/core": "^7.18.5", + "@sentry/core": "11.0.0", + "dotenv": "^17.4.2", + "glob": "^13.0.6", + "magic-string": "~0.30.8", + "oxc-parser": "^0.143.0", + "sentry": "^0.44.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "rollup": ">=3.2.0", + "webpack": ">=5.1.0" }, "peerDependenciesMeta": { - "@types/react": { + "rollup": { "optional": true }, - "@types/react-dom": { + "webpack": { "optional": true } } }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", - "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "node_modules/@sentry/conventions": { + "version": "0.23.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/conventions/-/conventions-0.23.0.tgz", + "integrity": "sha512-+Euyo7CNVecoOusgUnq27oV97oshifgceKfnGnuKfXrPUVWA/cisvii3E1rlvy2yBrKvl9K1gCEo1Jqog5zjEQ==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=14" } }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", - "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "node_modules/@sentry/core": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/core/-/core-11.0.0.tgz", + "integrity": "sha512-Sdmg21POKmNwZDz8pH/2FtilWeryh3yxhRxbfA1ph8SnX2DkXoqX6G+uXyjCSYagW2pefVklmSNeWHmUlSqioA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-use-effect-event": "0.0.5", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@sentry/conventions": "^0.23.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", - "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "node_modules/@sentry/feedback": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/feedback/-/feedback-11.0.0.tgz", + "integrity": "sha512-ZdUE6qSfMH4zturLHxg8idfI8cA+JdfrisqUQ6hE6MKaIAAZysNIPvvFysE2WQDjFbj3puS1iZm0jg0ym7kA5g==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@sentry/core": "11.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz", - "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==", + "node_modules/@sentry/nextjs": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/nextjs/-/nextjs-11.0.0.tgz", + "integrity": "sha512-ZiJrzm0hKf9plrRklaLU1QEGpXwYCPe18AzMEhfSNkorP3qJdi5X9xnZV52ap6zB4ypPru88aXUQtDGBdpl4cQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.4" + "@opentelemetry/api": "^1.9.1", + "@rollup/plugin-commonjs": "28.0.1", + "@sentry/browser-utils": "11.0.0", + "@sentry/bundler-plugins": "11.0.0", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0", + "@sentry/node": "11.0.0", + "@sentry/opentelemetry": "11.0.0", + "@sentry/react": "11.0.0", + "@sentry/server-runtime-injection": "11.0.0", + "@sentry/server-utils": "11.0.0", + "@sentry/vercel-edge": "11.0.0", + "rollup": "^4.60.3", + "stacktrace-parser": "^0.1.11" }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", - "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", - "license": "MIT", "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "next": "^14.0 || ^15.0.0-rc.0 || ^16.0.0-0" } }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", - "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "node_modules/@sentry/node": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/node/-/node-11.0.0.tgz", + "integrity": "sha512-ZHk2wYvnKPfaq05YHnva/a6EzZDLTG0EStcWnQdjSae/OxluwCfOfMqwlsKxaY8KDUkyqokTCUPqJITZpUH1Jw==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/bundler-plugins": "11.0.0", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0", + "@sentry/opentelemetry": "11.0.0", + "@sentry/server-runtime-injection": "11.0.0", + "@sentry/server-utils": "11.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", - "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "node_modules/@sentry/opentelemetry": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/opentelemetry/-/opentelemetry-11.0.0.tgz", + "integrity": "sha512-m2EtgppTtN6MxW0dKA45aZMI/xoFZYi2I2cvTIenFGTGLfxyjk6+YCbg0Jf/wdUWoZjR0k6kBBBIbv+zpuvTOA==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "dependencies": { + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" } }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", - "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "node_modules/@sentry/react": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/react/-/react-11.0.0.tgz", + "integrity": "sha512-YMF4t9659byD3rYZ9zjE5vdL3NUVFBEQfHzbMJf4kow7h7eyDdQi8GN4K1qsOFBim5C/7/ry0dkP1r4p7qtEWA==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.3" + "@sentry/browser": "11.0.0", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "17.x || 18.x || 19.x", + "react-router": "6.x || 7.x || 8.x" }, "peerDependenciesMeta": { - "@types/react": { + "react-router": { "optional": true } } }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", - "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "node_modules/@sentry/replay": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/replay/-/replay-11.0.0.tgz", + "integrity": "sha512-4/36Iw288SaWEwLjhpLRjCjL2UmSNEyU0TvLrAlzRAoYdksneEe7iQTSe7WD257buSgop0m+lauGRHQf7fD9qg==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@sentry/browser-utils": "11.0.0", + "@sentry/core": "11.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", - "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "node_modules/@sentry/replay-canvas": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/replay-canvas/-/replay-canvas-11.0.0.tgz", + "integrity": "sha512-cWAx4Fc+T36w4SwN0Ee35xVLrx+nt2kk2An3gKPqgRU0HwxA1w14fKAxcXfKlJcy/K7nnNMgerznvWKgBObenw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.10" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@sentry/core": "11.0.0", + "@sentry/replay": "11.0.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@radix-ui/rect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", - "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", - "license": "MIT" - }, - "node_modules/@react-aria/focus": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz", - "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==", - "license": "Apache-2.0", + "node_modules/@sentry/server-runtime-injection": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/server-runtime-injection/-/server-runtime-injection-11.0.0.tgz", + "integrity": "sha512-AE39xdGbi8WmcB9LtITuvJkp+sYxkPAR+3lRG4MYrJLVQS+YeYfn/AaZdEXOhnOagdpozsDbpEneGJn6vD8p6Q==", + "license": "MIT", "dependencies": { - "@swc/helpers": "^0.5.0", - "react-aria": "^3.48.0" + "@sentry/core": "11.0.0", + "@sentry/server-utils": "11.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@react-aria/interactions": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz", - "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==", - "license": "Apache-2.0", + "node_modules/@sentry/server-utils": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/server-utils/-/server-utils-11.0.0.tgz", + "integrity": "sha512-XlKDtLoNqOhI+TgCNywan/tFy0yMRzw5+8mdp96KNHtF/YwO9s6c7qZcBG0x8+XDyC0EoNXDlBPJOMEZo5lD4Q==", + "license": "MIT", "dependencies": { - "@react-types/shared": "^3.34.0", - "@swc/helpers": "^0.5.0", - "react-aria": "^3.48.0" + "@opentelemetry/api": "^1.9.1", + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, - "node_modules/@react-types/shared": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz", - "integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==", - "license": "Apache-2.0", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "node_modules/@sentry/vercel-edge": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/vercel-edge/-/vercel-edge-11.0.0.tgz", + "integrity": "sha512-tcDbhQygLnTeU9KNBjO4xYiVt5LIx7zsV7ywKznun6hQc+oWnJYbWrEJd+4ERSN5n9S368rX7Q7CDAmb8ioj+g==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/core": "11.0.0", + "@sentry/server-utils": "11.0.0" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" } }, "node_modules/@shikijs/core": { @@ -4033,7 +5277,7 @@ }, "node_modules/@tailwindcss/oxide-android-arm64": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" @@ -4067,7 +5311,7 @@ }, "node_modules/@tailwindcss/oxide-darwin-x64": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" @@ -4084,7 +5328,7 @@ }, "node_modules/@tailwindcss/oxide-freebsd-x64": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" @@ -4101,7 +5345,7 @@ }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" @@ -4118,7 +5362,7 @@ }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" @@ -4138,7 +5382,7 @@ }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" @@ -4158,7 +5402,7 @@ }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" @@ -4178,7 +5422,7 @@ }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" @@ -4198,7 +5442,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", @@ -4228,7 +5472,7 @@ }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" @@ -4245,7 +5489,7 @@ }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" @@ -4684,7 +5928,7 @@ }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", "cpu": [ "ppc64" @@ -4718,7 +5962,7 @@ }, "node_modules/@typescript/typescript-darwin-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", "cpu": [ "x64" @@ -4735,7 +5979,7 @@ }, "node_modules/@typescript/typescript-freebsd-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", "cpu": [ "arm64" @@ -4752,7 +5996,7 @@ }, "node_modules/@typescript/typescript-freebsd-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", "cpu": [ "x64" @@ -4769,7 +6013,7 @@ }, "node_modules/@typescript/typescript-linux-arm": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", "cpu": [ "arm" @@ -4786,7 +6030,7 @@ }, "node_modules/@typescript/typescript-linux-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", "cpu": [ "arm64" @@ -4803,7 +6047,7 @@ }, "node_modules/@typescript/typescript-linux-loong64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", "cpu": [ "loong64" @@ -4820,7 +6064,7 @@ }, "node_modules/@typescript/typescript-linux-mips64el": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", "cpu": [ "mips64el" @@ -4837,7 +6081,7 @@ }, "node_modules/@typescript/typescript-linux-ppc64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", "cpu": [ "ppc64" @@ -4854,7 +6098,7 @@ }, "node_modules/@typescript/typescript-linux-riscv64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", "cpu": [ "riscv64" @@ -4871,7 +6115,7 @@ }, "node_modules/@typescript/typescript-linux-s390x": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", "cpu": [ "s390x" @@ -4888,7 +6132,7 @@ }, "node_modules/@typescript/typescript-linux-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", "cpu": [ "x64" @@ -4905,7 +6149,7 @@ }, "node_modules/@typescript/typescript-netbsd-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", "cpu": [ "arm64" @@ -4922,7 +6166,7 @@ }, "node_modules/@typescript/typescript-netbsd-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", "cpu": [ "x64" @@ -4939,7 +6183,7 @@ }, "node_modules/@typescript/typescript-openbsd-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", "cpu": [ "arm64" @@ -4956,7 +6200,7 @@ }, "node_modules/@typescript/typescript-openbsd-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", "cpu": [ "x64" @@ -4973,7 +6217,7 @@ }, "node_modules/@typescript/typescript-sunos-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", "cpu": [ "x64" @@ -4990,7 +6234,7 @@ }, "node_modules/@typescript/typescript-win32-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", "cpu": [ "arm64" @@ -5007,7 +6251,7 @@ }, "node_modules/@typescript/typescript-win32-x64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "resolved": "https://sfw.security.sentry.io/npm/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", "cpu": [ "x64" @@ -5092,10 +6336,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://sfw.security.sentry.io/npm/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/baseline-browser-mapping": { - "version": "2.11.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", - "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "version": "2.11.25", + "resolved": "https://sfw.security.sentry.io/npm/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz", + "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -5104,6 +6357,51 @@ "node": ">=6.0.0" } }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://sfw.security.sentry.io/npm/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://sfw.security.sentry.io/npm/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5112,9 +6410,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001809", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", - "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "version": "1.0.30001810", + "resolved": "https://sfw.security.sentry.io/npm/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "funding": [ { "type": "opencollective", @@ -5234,6 +6532,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://sfw.security.sentry.io/npm/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://sfw.security.sentry.io/npm/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -5867,7 +7177,6 @@ "version": "17.4.2", "resolved": "https://sfw.security.sentry.io/npm/dotenv/-/dotenv-17.4.2.tgz", "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -6017,6 +7326,12 @@ } } }, + "node_modules/electron-to-chromium": { + "version": "1.5.438", + "resolved": "https://sfw.security.sentry.io/npm/electron-to-chromium/-/electron-to-chromium-1.5.438.tgz", + "integrity": "sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==", + "license": "ISC" + }, "node_modules/enhanced-resolve": { "version": "5.24.5", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", @@ -6096,6 +7411,15 @@ "@esbuild/win32-x64": "0.25.12" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://sfw.security.sentry.io/npm/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -6118,6 +7442,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://sfw.security.sentry.io/npm/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/eventsource-parser": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", @@ -6133,11 +7463,27 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://sfw.security.sentry.io/npm/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -6157,6 +7503,15 @@ "next": ">=13.2.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://sfw.security.sentry.io/npm/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-east-asian-width": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", @@ -6191,6 +7546,23 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://sfw.security.sentry.io/npm/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -6204,6 +7576,15 @@ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "license": "MIT" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://sfw.security.sentry.io/npm/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hast-util-from-dom": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", @@ -6567,6 +7948,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://sfw.security.sentry.io/npm/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -6577,12 +7967,42 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://sfw.security.sentry.io/npm/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://sfw.security.sentry.io/npm/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "license": "(AFL-2.1 OR BSD-3-Clause)" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://sfw.security.sentry.io/npm/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/katex": { "version": "0.16.47", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", @@ -6651,7 +8071,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" @@ -6693,7 +8113,7 @@ }, "node_modules/lightningcss-darwin-x64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" @@ -6714,7 +8134,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" @@ -6735,7 +8155,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" @@ -6756,7 +8176,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" @@ -6780,7 +8200,7 @@ }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" @@ -6804,7 +8224,7 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" @@ -6828,7 +8248,7 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" @@ -6852,7 +8272,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" @@ -6873,7 +8293,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "resolved": "https://sfw.security.sentry.io/npm/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" @@ -6908,6 +8328,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://sfw.security.sentry.io/npm/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/lucide-react": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.30.0.tgz", @@ -6921,7 +8350,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -7965,6 +9393,30 @@ ], "license": "MIT" }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://sfw.security.sentry.io/npm/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://sfw.security.sentry.io/npm/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -8097,6 +9549,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-releases": { + "version": "2.0.57", + "resolved": "https://sfw.security.sentry.io/npm/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/oniguruma-parser": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", @@ -8114,6 +9575,42 @@ "regex-recursion": "^6.0.2" } }, + "node_modules/oxc-parser": { + "version": "0.143.0", + "resolved": "https://sfw.security.sentry.io/npm/oxc-parser/-/oxc-parser-0.143.0.tgz", + "integrity": "sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.143.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.143.0", + "@oxc-parser/binding-android-arm64": "0.143.0", + "@oxc-parser/binding-darwin-arm64": "0.143.0", + "@oxc-parser/binding-darwin-x64": "0.143.0", + "@oxc-parser/binding-freebsd-x64": "0.143.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.143.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.143.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.143.0", + "@oxc-parser/binding-linux-arm64-musl": "0.143.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.143.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.143.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.143.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.143.0", + "@oxc-parser/binding-linux-x64-gnu": "0.143.0", + "@oxc-parser/binding-linux-x64-musl": "0.143.0", + "@oxc-parser/binding-openharmony-arm64": "0.143.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.143.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.143.0", + "@oxc-parser/binding-win32-x64-msvc": "0.143.0" + } + }, "node_modules/oxlint": { "version": "1.78.0", "resolved": "https://sfw.security.sentry.io/npm/oxlint/-/oxlint-1.78.0.tgz", @@ -8212,6 +9709,31 @@ "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://sfw.security.sentry.io/npm/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://sfw.security.sentry.io/npm/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/pg": { "version": "8.23.0", "resolved": "https://sfw.security.sentry.io/npm/pg/-/pg-8.23.0.tgz", @@ -8307,6 +9829,18 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://sfw.security.sentry.io/npm/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -8972,6 +10506,51 @@ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, + "node_modules/rollup": { + "version": "4.63.3", + "resolved": "https://sfw.security.sentry.io/npm/rollup/-/rollup-4.63.3.tgz", + "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.3", + "@rollup/rollup-android-arm64": "4.63.3", + "@rollup/rollup-darwin-arm64": "4.63.3", + "@rollup/rollup-darwin-x64": "4.63.3", + "@rollup/rollup-freebsd-arm64": "4.63.3", + "@rollup/rollup-freebsd-x64": "4.63.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.3", + "@rollup/rollup-linux-arm-musleabihf": "4.63.3", + "@rollup/rollup-linux-arm64-gnu": "4.63.3", + "@rollup/rollup-linux-arm64-musl": "4.63.3", + "@rollup/rollup-linux-loong64-gnu": "4.63.3", + "@rollup/rollup-linux-loong64-musl": "4.63.3", + "@rollup/rollup-linux-ppc64-gnu": "4.63.3", + "@rollup/rollup-linux-ppc64-musl": "4.63.3", + "@rollup/rollup-linux-riscv64-gnu": "4.63.3", + "@rollup/rollup-linux-riscv64-musl": "4.63.3", + "@rollup/rollup-linux-s390x-gnu": "4.63.3", + "@rollup/rollup-linux-x64-gnu": "4.63.3", + "@rollup/rollup-linux-x64-musl": "4.63.3", + "@rollup/rollup-openbsd-x64": "4.63.3", + "@rollup/rollup-openharmony-arm64": "4.63.3", + "@rollup/rollup-win32-arm64-msvc": "4.63.3", + "@rollup/rollup-win32-ia32-msvc": "4.63.3", + "@rollup/rollup-win32-x64-gnu": "4.63.3", + "@rollup/rollup-win32-x64-msvc": "4.63.3", + "fsevents": "~2.3.2" + } + }, "node_modules/roughjs": { "version": "4.6.6", "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", @@ -9002,6 +10581,27 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sentry": { + "version": "0.44.1", + "resolved": "https://sfw.security.sentry.io/npm/sentry/-/sentry-0.44.1.tgz", + "integrity": "sha512-LK+RJ2PFsJKM+P0D/zYC+M5gFB1ytfVONU9DVUqJu67YFlwnj3wGJkUzBhCS8R4iUqRdRTAYbFbRKTAe03rW/Q==", + "license": "FSL-1.1-Apache-2.0", + "bin": { + "sentry": "dist/bin.cjs" + }, + "engines": { + "node": ">=20.0" + } + }, "node_modules/sharp": { "version": "0.35.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", @@ -9140,6 +10740,18 @@ "node": ">= 10.x" } }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://sfw.security.sentry.io/npm/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/streamdown": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/streamdown/-/streamdown-2.5.0.tgz", @@ -9241,6 +10853,21 @@ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", "license": "MIT" }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://sfw.security.sentry.io/npm/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/swr": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/swr/-/swr-2.5.0.tgz", @@ -9859,6 +11486,15 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://sfw.security.sentry.io/npm/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/typescript": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", @@ -10025,6 +11661,36 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://sfw.security.sentry.io/npm/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -10164,6 +11830,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/web-vitals": { + "version": "6.2.2", + "resolved": "https://sfw.security.sentry.io/npm/web-vitals/-/web-vitals-6.2.2.tgz", + "integrity": "sha512-oto5x6dLEgrRqfcWed+pZEUb2q6ikbFmqF54CRDhI/QGbn+qxC49b4C4OkbH+kb9C3a8shpFD3SgR9kRcs80ZQ==", + "license": "Apache-2.0" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://sfw.security.sentry.io/npm/xtend/-/xtend-4.0.2.tgz", @@ -10173,6 +11845,12 @@ "node": ">=0.4" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://sfw.security.sentry.io/npm/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", diff --git a/apps/storefront/package.json b/apps/storefront/package.json index 70870cf..16ac9fa 100644 --- a/apps/storefront/package.json +++ b/apps/storefront/package.json @@ -18,12 +18,13 @@ "@ai-sdk/react": "4.0.67", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", + "@openrouter/ai-sdk-provider": "3.0.0", + "@sentry/nextjs": "11.0.0", "@streamdown/cjk": "^1.0.3", "@streamdown/code": "^1.1.1", "@streamdown/math": "^1.0.2", "@streamdown/mermaid": "^1.0.2", "ai": "7.0.64", - "@openrouter/ai-sdk-provider": "3.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/apps/storefront/sentry.server.config.ts b/apps/storefront/sentry.server.config.ts new file mode 100644 index 0000000..051cea3 --- /dev/null +++ b/apps/storefront/sentry.server.config.ts @@ -0,0 +1,7 @@ +import * as Sentry from "@sentry/nextjs"; + +// v11 records AI inputs, outputs, and the user by default — no opt-in flags needed. +Sentry.init({ + dsn: process.env.SENTRY_DSN, + tracesSampleRate: 1, +}); diff --git a/labs/04-slack-agent.md b/labs/04-slack-agent.md index 84e67a5..c0003d3 100644 --- a/labs/04-slack-agent.md +++ b/labs/04-slack-agent.md @@ -1,13 +1,15 @@ -# Lab 4. The same agent in Slack +# Lab 4. A different agent in Slack (10 minutes, follow-along) 10 minutes. Follow-along. The presenter drives; you can repeat it after the workshop with `apps/slack-agent/README.md`. `apps/slack-agent` is a Bolt app on Socket Mode. It answers direct messages -and mentions with the storefront's four tools and the same Postgres database. -Each Slack thread is one conversation. The agent sets a thread status at -once, streams its reply, and shows each tool call as a task card. There is no -browser, no HTTP request, and no Sentry. +and mentions with four tools over a Postgres analytics warehouse on Neon, +seeded by `npm run db:seed`, the same database as the storefront. Each Slack +thread is one conversation. The agent sets a thread status at once, streams +its reply, and shows each tool call as a task card, and ends the reply with a +chart and a table built from the tool results. There is no browser, no +HTTP request, and no Sentry. What changes compared with the storefront: @@ -15,6 +17,9 @@ What changes compared with the storefront: - The conversation id is the Slack thread timestamp. - The user is the Slack user who wrote the message. - There is no session replay. The trace is all you get. +- A direct message is private, so the agent never records the prompt or the + reply there. A public or private channel is visible to more than one + person, so it does. ## Step 1. Instrument by prompt @@ -22,16 +27,21 @@ Start your coding agent in `apps/slack-agent` and send: > Instrument this Node.js Slack bot with Sentry. Create a new Sentry project > for it in my organization. I need: -> - Agent tracing for the Vercel AI SDK call in `src/agent.ts`, with inputs -> and outputs recorded. +> - Agent tracing for the Vercel AI SDK call in `src/agent.ts`. > - One transaction per Slack message handled in `src/app.ts`, so each -> message shows as one trace with the model calls and tool calls inside it. +> message shows as one trace with the model call and its tool calls inside +> it. > - The Slack thread timestamp set as the conversation id, and the Slack > user id set as the Sentry user. -> - Database spans for the Postgres queries under `../storefront/lib/db`. -> - Full trace sampling for the workshop. -> Use the 11.0 release candidate of the Sentry SDK, npm tag `next`. Add only -> what these points need. +> - Prompts and replies recorded only when the message is in a public or +> private channel, never in a direct message. +> - One span per warehouse query in `src/analytics/tools.ts`, tagged with +> the metric name, the date range length, and the row count, so a direct +> message trace still shows something useful without the prompt and the +> reply. +> - The release read from `SENTRY_RELEASE`, and full trace sampling for the +> workshop. +> Use the 11.0 release of the Sentry SDK. Add only what these points need. > The process starts with `npm run dev`. When done, start it, wait for the > "connected over Socket Mode" line, and tell me how to confirm the first > trace arrived. @@ -43,6 +53,8 @@ Watch for: message handler. - Whether it imports Sentry before Bolt and the AI SDK. Import order decides what gets instrumented. +- Whether it ties content recording to the channel type, not to a global + setting. A DM and a channel message go through the same code path. ## If your agent is still running @@ -57,16 +69,38 @@ and continue with Step 2. ## Step 2. Verify -Send the bot a direct message: "what did I order last?" Then ask your agent: +Send the bot a message in a channel it's in: "How did signups do last week +compared with the week before?" Then ask your agent: > Find the latest trace in the slack-agent project. Show the transaction > name, the conversation id, the user, and every gen_ai and tool span with > its token counts. -You must see one trace for the message with the model call and the -`getAccountInfo` tool span nested inside it. +You must see one trace for the message, with the model call and two +`analytics.scan` spans nested inside it: `compare_periods` scans the current +week and the previous week. Each `analytics.scan` span has a `db` child span +from the pg integration, for the Postgres query itself. The dev script starts +Node with `--import ./src/instrument.ts`, so Sentry loads before `pg`. A +runner that compiles TypeScript itself, such as tsx, skips that hook and +shows no db spans. -## Step 3. Ship the regression +## Step 3. The same question in a channel and in a DM + +Ask the exact same question two ways: once as a mention in a public or +private channel, and once as a direct message to the bot. Open both traces +and compare them. + +The channel trace shows the prompt, the reply, and the arguments each tool +call received. The DM trace shows none of that. It still shows two +`analytics.scan` spans, each with the metric name, the date range length, +the row count, and the model call's token counts and cached tokens. + +Ask your agent: + +> Compare the channel trace and the DM trace for this question. What shows +> up in one and not the other, and why? + +## Step 4. Ship the regression The presenter tags a release and applies `apps/slack-agent/regressions/drop-prompt-cache.patch`, then restarts the bot @@ -89,13 +123,18 @@ regression from the talk, now in your own project. prompt asks for one transaction per message. - Conversation id and user come from the platform. Tell the agent where they are. +- Content recording can follow a rule the platform already gives you, such + as channel type, instead of an all-or-nothing switch. +- A span's attributes can stay useful on their own, even when its inputs and + outputs are never recorded. - A cache regression shows up as a change in one attribute per release. It is invisible without agent tracing. -## Reference: the same bot on eve +## Reference: an earlier version of this bot on eve -The reference repo has this assistant on Vercel's eve framework, in -`slack-agent-eve` (Sentry SDK inside eve's OpenTelemetry pipeline) and -`slack-agent-eve-otel` (OTLP export to Sentry). eve is webhook-based and -needs a Vercel deploy, so the lab uses Bolt. Read those two when your own -agent framework owns the trace pipeline. +The reference repo has an earlier version of this assistant, the one that +shared the storefront's product catalog and orders, on Vercel's eve +framework, in `slack-agent-eve` (Sentry SDK inside eve's OpenTelemetry +pipeline) and `slack-agent-eve-otel` (OTLP export to Sentry). eve is +webhook-based and needs a Vercel deploy, so the lab uses Bolt. Read those two +when your own agent framework owns the trace pipeline. diff --git a/scripts/dashboards/agent-critical-paths.mjs b/scripts/dashboards/agent-critical-paths.mjs index 6bfeae9..504e5ab 100755 --- a/scripts/dashboards/agent-critical-paths.mjs +++ b/scripts/dashboards/agent-critical-paths.mjs @@ -42,9 +42,18 @@ function widget(title, displayType, query, layout, extra = {}) { }, ], layout: { minH: 1, ...layout }, + ...(extra.thresholds ? { thresholds: extra.thresholds } : {}), }; } +// Big-number tiles turn red below max1 (or above it, with polarity "-") and +// green past max2; the numbers come from the agent-tracing talk. +function tile(title, aggregate, conditions, x, [max1, max2], preferredPolarity, unit = null) { + return widget(title, "big_number", { aggregates: [aggregate], conditions }, { x, y: 0, w: 2, h: 1 }, { + thresholds: { max_values: { max1, max2 }, unit, preferredPolarity }, + }); +} + const dashboard = { title, // All projects (-1) plus a project.id filter in each widget. Binding the @@ -54,12 +63,15 @@ const dashboard = { period: "24h", filters: {}, widgets: [ + tile("Cache hit rate, %", CACHE_HIT, `${MODEL_CALLS} has:gen_ai.usage.input_tokens`, 0, [50, 80], "+"), + tile("Input tokens per model call", "avg(gen_ai.usage.input_tokens)", `${MODEL_CALLS} has:gen_ai.usage.input_tokens`, 2, [20000, 50000], "-"), + tile("Longest tool call", "max(span.duration)", TOOL_CALLS, 4, [2, 5], "-", "minute"), widget("Cache hit rate by release", "line", { columns: ["release"], aggregates: [CACHE_HIT], aliases: ["", "cached %"], conditions: MODEL_CALLS }, - { x: 0, y: 0, w: 3, h: 2 }), + { x: 0, y: 1, w: 3, h: 2 }), widget("Input tokens by release", "bar", { columns: ["release"], aggregates: ["sum(gen_ai.usage.input_tokens)"], conditions: MODEL_CALLS }, - { x: 3, y: 0, w: 3, h: 2 }), + { x: 3, y: 1, w: 3, h: 2 }), widget("Most expensive conversations", "table", { columns: ["gen_ai.conversation.id", "user.id"], @@ -67,7 +79,7 @@ const dashboard = { conditions: MODEL_CALLS, orderby: "-sum(gen_ai.usage.input_tokens)", }, - { x: 0, y: 2, w: 4, h: 2 }, { limit: 10 }), + { x: 0, y: 3, w: 4, h: 2 }, { limit: 10 }), widget("Tool calls and failures", "table", { columns: ["gen_ai.tool.name"], @@ -75,10 +87,10 @@ const dashboard = { conditions: TOOL_CALLS, orderby: "-failure_count()", }, - { x: 4, y: 2, w: 2, h: 2 }), + { x: 4, y: 3, w: 2, h: 2 }), widget("Model latency p95", "line", { columns: ["gen_ai.request.model"], aggregates: ["p95(span.duration)"], conditions: MODEL_CALLS }, - { x: 0, y: 4, w: 6, h: 2 }), + { x: 0, y: 5, w: 6, h: 2 }), ], }; diff --git a/solutions/04-slack-agent/README.md b/solutions/04-slack-agent/README.md index 7fd976b..47e3281 100644 --- a/solutions/04-slack-agent/README.md +++ b/solutions/04-slack-agent/README.md @@ -1,27 +1,37 @@ # Solution: instrument the slack-agent `instrumentation.patch` adds Sentry (`@sentry/node` 11.0.0) to -`apps/slack-agent`, wired for agent tracing on the Vercel AI SDK call. +`apps/slack-agent`, the Lighthouse Analytics bot. It wires up one trace per +Slack message, with a low-cardinality `analytics.scan` span per warehouse +query so a direct message still shows something useful even though its +prompt and reply are never recorded. ## Files it adds or changes -- `src/instrument.ts` — Sentry init: DSN and tracing, plus - `consoleLoggingIntegration()` so the bot's `console.log`/`console.error` - lines become Sentry logs. v11 enables the Vercel AI integration and records - AI inputs, outputs, and the user by default, so no opt-in flags are needed. - Postgres spans come from the SDK's built-in pg integration. -- `src/app.ts` — imports `instrument.ts` first, before `@slack/bolt` and the - agent. Wraps the body of the shared `respond()` (mention, DM, and assistant - thread) in one `Sentry.startSpan` (a root span, which is a segment in v11) - tagged with the Slack channel and the thread timestamp as the conversation - id. The streamed model call and its tool calls nest inside it. Scopes the - Sentry user to the Slack user per message and captures errors before the - existing error reply. -- `src/agent.ts` — unchanged; v11 records AI telemetry by default. -- `.env.example` — adds `SENTRY_DSN`, `SENTRY_ENVIRONMENT`, `SENTRY_RELEASE` - (the last two are read automatically by the SDK). -- `README.md` — adds a Sentry section. +- `src/instrument.ts` — Sentry init: DSN, release, environment, and tracing, + plus a `dataCollection` block that turns AI content recording off by + default. `src/app.ts` imports it first. +- `src/app.ts` — wraps the body of the shared `respond()` (mention and DM) in + one root `Sentry.startSpan`, tagged with the Slack channel, the thread + timestamp as the conversation id, and whether content recording is on for + that channel. The span runs inside `Sentry.withIsolationScope` and + `Sentry.startNewTrace`, because Bolt handles every event in one long-lived + process and each message would otherwise join the previous message's + trace. Scopes the Sentry user to the Slack user per message and captures + errors before the existing error reply. +- `src/agent.ts` — passes a per-call `telemetry` override to `streamText` so + the AI SDK's own inputs/outputs recording follows the same channel-vs-DM + rule as the root span. +- `src/analytics/tools.ts` — wraps each warehouse `scan()` in its own + `Sentry.startSpan` (`analytics.scan`), with the metric, the range length, + and the row count as attributes. The `pg` integration instruments the + query itself, so each scan gets a `db` child span for free. The dev script + starts Node with `--import ./src/instrument.ts`, so Sentry loads before + `pg`. A runner that compiles TypeScript itself, such as tsx, skips that + hook and shows no db spans. +- `.env.example` — adds `SENTRY_DSN`, `SENTRY_ENVIRONMENT`, `SENTRY_RELEASE`. - `package.json` / `package-lock.json` — adds the `@sentry/node` dependency. + The dev script gains `--import ./src/instrument.ts`. SDK: @sentry/node 11.0.0 @@ -29,8 +39,8 @@ SDK: @sentry/node 11.0.0 ``` SENTRY_DSN= # this project's DSN -SENTRY_ENVIRONMENT= # optional; read automatically by the SDK -SENTRY_RELEASE= # optional; read automatically by the SDK +SENTRY_ENVIRONMENT= # optional; defaults to "development" +SENTRY_RELEASE= # optional; tags every span and error with it ``` ## Apply / undo diff --git a/solutions/04-slack-agent/instrumentation.patch b/solutions/04-slack-agent/instrumentation.patch index dc606fc..140b179 100644 --- a/solutions/04-slack-agent/instrumentation.patch +++ b/solutions/04-slack-agent/instrumentation.patch @@ -1,40 +1,22 @@ -diff --git a/apps/slack-agent/.env.example b/apps/slack-agent/.env.example -index 0000000..0000000 100644 ---- a/apps/slack-agent/.env.example -+++ b/apps/slack-agent/.env.example -@@ -14,3 +14,10 @@ DATABASE_URL= - # Optional. One of the SUPPORTED_MODELS in ../storefront/lib/ai/models.ts. - # Defaults to anthropic/claude-sonnet-5. +diff --git b/apps/slack-agent/.env.example a/apps/slack-agent/.env.example +index ff0cdae..6ff18d6 100644 +--- b/apps/slack-agent/.env.example ++++ a/apps/slack-agent/.env.example +@@ -15,3 +15,10 @@ DATABASE_URL= + # openai/gpt-5-mini, google/gemini-2.5-flash. Defaults to + # anthropic/claude-sonnet-5. # OPENROUTER_MODEL=anthropic/claude-sonnet-5 + +# Sentry DSN for this project (Project Settings > Client Keys) +SENTRY_DSN= -+# Optional. Read automatically by the SDK; not passed to Sentry.init. ++# Optional. Defaults to "development". +SENTRY_ENVIRONMENT= -+# Optional. Read automatically by the SDK; not passed to Sentry.init. ++# Optional. Tags every span and error with the release that produced it. +SENTRY_RELEASE= -diff --git a/apps/slack-agent/README.md b/apps/slack-agent/README.md -index 0000000..0000000 100644 ---- a/apps/slack-agent/README.md -+++ b/apps/slack-agent/README.md -@@ -43,4 +43,12 @@ app created from an older manifest must have its manifest replaced on the App - Manifest page at api.slack.com, then be reinstalled. Slack does not let an - app go back to the older assistant experience. - --Sentry is added to this app in `labs/04-slack-agent.md`. -+## Sentry -+ -+Set `SENTRY_DSN` in `.env.local` (`SENTRY_ENVIRONMENT` and `SENTRY_RELEASE` -+are optional; the SDK reads them automatically). Each handled Slack message -+becomes one trace: a root `slack.message` span tagged with the Slack channel -+and the thread timestamp as the conversation id, with the model call and its -+tool calls nested inside as `gen_ai` spans, and Postgres queries from `lib/db` -+as `db.query` spans. The Sentry user is the Slack user who sent the message. -+Errors go to Sentry as issues. -diff --git a/apps/slack-agent/package-lock.json b/apps/slack-agent/package-lock.json -index 0000000..0000000 100644 ---- a/apps/slack-agent/package-lock.json -+++ b/apps/slack-agent/package-lock.json +diff --git b/apps/slack-agent/package-lock.json a/apps/slack-agent/package-lock.json +index 5faac5c..1c6c73b 100644 +--- b/apps/slack-agent/package-lock.json ++++ a/apps/slack-agent/package-lock.json @@ -7,6 +7,7 @@ "name": "slack-agent", "dependencies": { @@ -43,49 +25,21 @@ index 0000000..0000000 100644 "@slack/bolt": "^4.4.0", "ai": "7.0.64", "dotenv": "17.4.2", -@@ -41,7 +42,19 @@ +@@ -70,17 +71,745 @@ "zod": "^3.25.76 || ^4.1.8" } }, -- "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { -+ "node_modules/@ai-sdk/provider": { -+ "version": "4.0.7", -+ "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider/-/provider-4.0.7.tgz", -+ "integrity": "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==", -+ "license": "Apache-2.0", -+ "dependencies": { -+ "json-schema": "^0.4.0" -+ }, -+ "engines": { -+ "node": ">=22" -+ } -+ }, -+ "node_modules/@ai-sdk/provider-utils": { - "version": "5.0.27", - "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider-utils/-/provider-utils-5.0.27.tgz", - "integrity": "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw==", -@@ -60,16 +73,228 @@ - "zod": "^3.25.76 || ^4.1.8" - } - }, -- "node_modules/@ai-sdk/provider": { -- "version": "4.0.7", -- "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider/-/provider-4.0.7.tgz", -- "integrity": "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==", -- "license": "Apache-2.0", + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://sfw.security.sentry.io/npm/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", - "dependencies": { -- "json-schema": "^0.4.0" ++ "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" - }, - "engines": { -- "node": ">=22" ++ }, ++ "engines": { + "node": ">=6.9.0" + } + }, @@ -297,53 +251,6 @@ index 0000000..0000000 100644 + }, + "engines": { + "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { -@@ -480,51 +705,555 @@ - "node": ">=18" - } - }, -- "node_modules/@esbuild/win32-ia32": { -- "version": "0.28.2", -- "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", -- "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", -- "cpu": [ -- "ia32" -- ], -- "dev": true, -+ "node_modules/@esbuild/win32-ia32": { -+ "version": "0.28.2", -+ "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", -+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", -+ "cpu": [ -+ "ia32" -+ ], -+ "dev": true, -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "win32" -+ ], -+ "engines": { -+ "node": ">=18" -+ } -+ }, -+ "node_modules/@esbuild/win32-x64": { -+ "version": "0.28.2", -+ "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", -+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", -+ "cpu": [ -+ "x64" -+ ], -+ "dev": true, -+ "license": "MIT", -+ "optional": true, -+ "os": [ -+ "win32" -+ ], -+ "engines": { -+ "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { @@ -391,12 +298,13 @@ index 0000000..0000000 100644 + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, -+ "node_modules/@openrouter/ai-sdk-provider": { -+ "version": "3.0.0", -+ "resolved": "https://sfw.security.sentry.io/npm/@openrouter/ai-sdk-provider/-/ai-sdk-provider-3.0.0.tgz", -+ "integrity": "sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==", -+ "license": "Apache-2.0", -+ "engines": { + "node_modules/@openrouter/ai-sdk-provider": { + "version": "3.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@openrouter/ai-sdk-provider/-/ai-sdk-provider-3.0.0.tgz", + "integrity": "sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==", + "license": "Apache-2.0", + "engines": { +- "node": ">=22" + "node": ">=22" + }, + "peerDependencies": { @@ -767,8 +675,10 @@ index 0000000..0000000 100644 + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" -+ }, -+ "peerDependencies": { + }, + "peerDependencies": { +- "ai": "^7.0.0", +- "zod": "^3.25.76 || ^4.1.8" + "rollup": ">=3.2.0", + "webpack": ">=5.1.0" + }, @@ -794,36 +704,19 @@ index 0000000..0000000 100644 + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/core/-/core-11.0.0.tgz", + "integrity": "sha512-Sdmg21POKmNwZDz8pH/2FtilWeryh3yxhRxbfA1ph8SnX2DkXoqX6G+uXyjCSYagW2pefVklmSNeWHmUlSqioA==", - "license": "MIT", -- "optional": true, -- "os": [ -- "win32" -- ], ++ "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.23.0" + }, - "engines": { -- "node": ">=18" ++ "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" - } - }, -- "node_modules/@esbuild/win32-x64": { -- "version": "0.28.2", -- "resolved": "https://sfw.security.sentry.io/npm/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", -- "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", -- "cpu": [ -- "x64" -- ], -- "dev": true, ++ } ++ }, + "node_modules/@sentry/node": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/node/-/node-11.0.0.tgz", + "integrity": "sha512-ZHk2wYvnKPfaq05YHnva/a6EzZDLTG0EStcWnQdjSae/OxluwCfOfMqwlsKxaY8KDUkyqokTCUPqJITZpUH1Jw==", - "license": "MIT", -- "optional": true, -- "os": [ -- "win32" -- ], ++ "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/bundler-plugins": "11.0.0", @@ -833,16 +726,10 @@ index 0000000..0000000 100644 + "@sentry/server-runtime-injection": "11.0.0", + "@sentry/server-utils": "11.0.0" + }, - "engines": { -- "node": ">=18" ++ "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" - } - }, -- "node_modules/@openrouter/ai-sdk-provider": { -- "version": "3.0.0", -- "resolved": "https://sfw.security.sentry.io/npm/@openrouter/ai-sdk-provider/-/ai-sdk-provider-3.0.0.tgz", -- "integrity": "sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==", -- "license": "Apache-2.0", ++ } ++ }, + "node_modules/@sentry/opentelemetry": { + "version": "11.0.0", + "resolved": "https://sfw.security.sentry.io/npm/@sentry/opentelemetry/-/opentelemetry-11.0.0.tgz", @@ -852,13 +739,10 @@ index 0000000..0000000 100644 + "@sentry/conventions": "^0.23.0", + "@sentry/core": "11.0.0" + }, - "engines": { -- "node": ">=22" ++ "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" - }, - "peerDependencies": { -- "ai": "^7.0.0", -- "zod": "^3.25.76 || ^4.1.8" ++ }, ++ "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, @@ -890,33 +774,7 @@ index 0000000..0000000 100644 } }, "node_modules/@slack/bolt": { -@@ -1177,25 +1906,6 @@ - "zod": "^3.25.76 || ^4.1.8" - } - }, -- "node_modules/ai/node_modules/@ai-sdk/provider-utils": { -- "version": "5.0.27", -- "resolved": "https://sfw.security.sentry.io/npm/@ai-sdk/provider-utils/-/provider-utils-5.0.27.tgz", -- "integrity": "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw==", -- "license": "Apache-2.0", -- "dependencies": { -- "@ai-sdk/provider": "4.0.7", -- "@standard-schema/spec": "^1.1.0", -- "@workflow/serde": "4.1.0", -- "eventsource-parser": "^3.0.8", -- "undici": "^7.28.0" -- }, -- "engines": { -- "node": ">=22" -- }, -- "peerDependencies": { -- "zod": "^3.25.76 || ^4.1.8" -- } -- }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://sfw.security.sentry.io/npm/asynckit/-/asynckit-0.4.0.tgz", -@@ -1214,6 +1924,27 @@ +@@ -751,6 +1480,27 @@ "proxy-from-env": "^2.1.0" } }, @@ -944,7 +802,7 @@ index 0000000..0000000 100644 "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://sfw.security.sentry.io/npm/body-parser/-/body-parser-2.3.0.tgz", -@@ -1251,6 +1982,51 @@ +@@ -788,6 +1538,51 @@ "url": "https://opencollective.com/express" } }, @@ -996,7 +854,7 @@ index 0000000..0000000 100644 "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://sfw.security.sentry.io/npm/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", -@@ -1295,6 +2071,26 @@ +@@ -832,6 +1627,26 @@ "url": "https://github.com/sponsors/ljharb" } }, @@ -1023,7 +881,7 @@ index 0000000..0000000 100644 "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://sfw.security.sentry.io/npm/combined-stream/-/combined-stream-1.0.8.tgz", -@@ -1329,6 +2125,12 @@ +@@ -866,6 +1681,12 @@ "node": ">= 0.6" } }, @@ -1036,7 +894,7 @@ index 0000000..0000000 100644 "node_modules/cookie": { "version": "0.7.2", "resolved": "https://sfw.security.sentry.io/npm/cookie/-/cookie-0.7.2.tgz", -@@ -1548,6 +2350,12 @@ +@@ -960,6 +1781,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, @@ -1049,8 +907,8 @@ index 0000000..0000000 100644 "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://sfw.security.sentry.io/npm/encodeurl/-/encodeurl-2.0.0.tgz", -@@ -1644,6 +2452,15 @@ - "@esbuild/win32-x64": "0.28.2" +@@ -1014,6 +1841,15 @@ + "node": ">= 0.4" } }, + "node_modules/escalade": { @@ -1065,7 +923,7 @@ index 0000000..0000000 100644 "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://sfw.security.sentry.io/npm/escape-html/-/escape-html-1.0.3.tgz", -@@ -1837,6 +2654,15 @@ +@@ -1192,6 +2028,15 @@ "url": "https://github.com/sponsors/ljharb" } }, @@ -1081,7 +939,7 @@ index 0000000..0000000 100644 "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://sfw.security.sentry.io/npm/get-intrinsic/-/get-intrinsic-1.3.0.tgz", -@@ -1874,6 +2700,23 @@ +@@ -1229,6 +2074,23 @@ "node": ">= 0.4" } }, @@ -1105,7 +963,7 @@ index 0000000..0000000 100644 "node_modules/gopd": { "version": "1.2.0", "resolved": "https://sfw.security.sentry.io/npm/gopd/-/gopd-1.2.0.tgz", -@@ -1886,6 +2729,15 @@ +@@ -1241,6 +2103,15 @@ "url": "https://github.com/sponsors/ljharb" } }, @@ -1121,7 +979,7 @@ index 0000000..0000000 100644 "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://sfw.security.sentry.io/npm/has-symbols/-/has-symbols-1.1.0.tgz", -@@ -2013,12 +2865,42 @@ +@@ -1368,12 +2239,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, @@ -1164,26 +1022,7 @@ index 0000000..0000000 100644 "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://sfw.security.sentry.io/npm/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", -@@ -2041,6 +2923,18 @@ - "npm": ">=6" - } - }, -+ "node_modules/jsonwebtoken/node_modules/semver": { -+ "version": "7.8.5", -+ "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-7.8.5.tgz", -+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", -+ "license": "ISC", -+ "bin": { -+ "semver": "bin/semver.js" -+ }, -+ "engines": { -+ "node": ">=10" -+ } -+ }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://sfw.security.sentry.io/npm/jwa/-/jwa-2.0.1.tgz", -@@ -2104,6 +2998,24 @@ +@@ -1471,6 +2372,24 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, @@ -1208,7 +1047,7 @@ index 0000000..0000000 100644 "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://sfw.security.sentry.io/npm/math-intrinsics/-/math-intrinsics-1.1.0.tgz", -@@ -2163,6 +3075,30 @@ +@@ -1530,6 +2449,30 @@ "url": "https://opencollective.com/express" } }, @@ -1239,7 +1078,7 @@ index 0000000..0000000 100644 "node_modules/ms": { "version": "2.1.3", "resolved": "https://sfw.security.sentry.io/npm/ms/-/ms-2.1.3.tgz", -@@ -2198,6 +3134,15 @@ +@@ -1565,6 +2508,15 @@ "url": "https://opencollective.com/express" } }, @@ -1255,7 +1094,7 @@ index 0000000..0000000 100644 "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://sfw.security.sentry.io/npm/object-inspect/-/object-inspect-1.13.4.tgz", -@@ -2231,6 +3176,42 @@ +@@ -1598,6 +2550,42 @@ "wrappy": "1" } }, @@ -1298,7 +1137,7 @@ index 0000000..0000000 100644 "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://sfw.security.sentry.io/npm/p-finally/-/p-finally-1.0.0.tgz", -@@ -2296,6 +3277,31 @@ +@@ -1663,6 +2651,31 @@ "node": ">= 0.8" } }, @@ -1330,7 +1169,7 @@ index 0000000..0000000 100644 "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://sfw.security.sentry.io/npm/path-to-regexp/-/path-to-regexp-8.4.2.tgz", -@@ -2395,6 +3401,12 @@ +@@ -1762,6 +2775,12 @@ "split2": "^4.1.0" } }, @@ -1343,26 +1182,23 @@ index 0000000..0000000 100644 "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://sfw.security.sentry.io/npm/postgres-array/-/postgres-array-2.0.0.tgz", -@@ -2556,15 +3568,12 @@ +@@ -1922,6 +2941,15 @@ + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/semver": { -- "version": "7.8.5", -- "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-7.8.5.tgz", -- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", ++ "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://sfw.security.sentry.io/npm/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" -- }, -- "engines": { -- "node": ">=10" - } - }, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver.js" ++ } ++ }, "node_modules/send": { -@@ -2593,6 +3602,18 @@ + "version": "1.2.1", + "resolved": "https://sfw.security.sentry.io/npm/send/-/send-1.2.1.tgz", +@@ -1948,6 +2976,18 @@ "url": "https://opencollective.com/express" } }, @@ -1381,7 +1217,7 @@ index 0000000..0000000 100644 "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://sfw.security.sentry.io/npm/serve-static/-/serve-static-2.2.1.tgz", -@@ -2708,6 +3729,21 @@ +@@ -2063,6 +3103,21 @@ "node": ">= 0.8" } }, @@ -1403,7 +1239,7 @@ index 0000000..0000000 100644 "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://sfw.security.sentry.io/npm/toidentifier/-/toidentifier-1.0.1.tgz", -@@ -2835,6 +3871,36 @@ +@@ -2171,6 +3226,36 @@ "node": ">= 0.8" } }, @@ -1440,7 +1276,7 @@ index 0000000..0000000 100644 "node_modules/vary": { "version": "1.1.2", "resolved": "https://sfw.security.sentry.io/npm/vary/-/vary-1.1.2.tgz", -@@ -2880,6 +3946,12 @@ +@@ -2216,6 +3301,12 @@ "node": ">=0.4" } }, @@ -1453,11 +1289,20 @@ index 0000000..0000000 100644 "node_modules/zod": { "version": "4.4.3", "resolved": "https://sfw.security.sentry.io/npm/zod/-/zod-4.4.3.tgz", -diff --git a/apps/slack-agent/package.json b/apps/slack-agent/package.json -index 0000000..0000000 100644 ---- a/apps/slack-agent/package.json -+++ b/apps/slack-agent/package.json -@@ -12,6 +12,7 @@ +diff --git b/apps/slack-agent/package.json a/apps/slack-agent/package.json +index d62b48b..94f19e0 100644 +--- b/apps/slack-agent/package.json ++++ a/apps/slack-agent/package.json +@@ -6,7 +6,7 @@ + "node": ">=22.18" + }, + "scripts": { +- "dev": "node --env-file=.env.local src/app.ts", ++ "dev": "node --env-file=.env.local --import ./src/instrument.ts src/app.ts", + "typecheck": "tsc --noEmit", + "test": "node --test src/analytics/store.test.ts src/analytics/blocks.test.ts", + "manifest:link": "node scripts/manifest-link.mjs", +@@ -14,6 +14,7 @@ }, "dependencies": { "@openrouter/ai-sdk-provider": "3.0.0", @@ -1465,84 +1310,133 @@ index 0000000..0000000 100644 "@slack/bolt": "^4.4.0", "ai": "7.0.64", "dotenv": "17.4.2", -diff --git a/apps/slack-agent/src/app.ts b/apps/slack-agent/src/app.ts -index 0000000..0000000 100644 ---- a/apps/slack-agent/src/app.ts -+++ b/apps/slack-agent/src/app.ts +diff --git b/apps/slack-agent/src/agent.ts a/apps/slack-agent/src/agent.ts +index d247f93..b4285c1 100644 +--- b/apps/slack-agent/src/agent.ts ++++ a/apps/slack-agent/src/agent.ts +@@ -55,10 +55,12 @@ export function streamAnswer({ + messages, + conversationId, + abortSignal, ++ recordContent, + }: { + messages: ModelMessage[]; + conversationId: string; + abortSignal?: AbortSignal; ++ recordContent: boolean; + }) { + return streamText({ + model: openrouter.chat(resolveModel()), +@@ -71,5 +73,12 @@ export function streamAnswer({ + abortSignal, + tools: analyticsTools, + stopWhen: isStepCount(6), ++ // functionId names the agent in the AI SDK's telemetry. ++ // A per-call boolean beats the `dataCollection` default set in instrument.ts. ++ telemetry: { ++ functionId: "slack-analytics-assistant", ++ recordInputs: recordContent, ++ recordOutputs: recordContent, ++ }, + }); + } +diff --git b/apps/slack-agent/src/analytics/tools.ts a/apps/slack-agent/src/analytics/tools.ts +index e2b5959..6e67576 100644 +--- b/apps/slack-agent/src/analytics/tools.ts ++++ a/apps/slack-agent/src/analytics/tools.ts +@@ -1,3 +1,4 @@ ++import * as Sentry from "@sentry/node"; + import { tool } from "ai"; + import { z } from "zod"; + import { METRICS, scan, total, type MetricDef, type Row } from "./store.ts"; +@@ -57,14 +58,32 @@ function changeOf( + }; + } + ++// A scan's arguments and row count are the only things that stay useful in ++// a trace where inputs and outputs are not recorded - a direct message. So ++// every scan gets its own low-cardinality span, wrapped here once for all ++// four tools, instead of the full query and its result. + async function scannedRows(args: { + metric: string; + from: string; + to: string; + groupBy?: string; + }): Promise { +- const { rows } = await scan(args); +- return rows; ++ return Sentry.startSpan( ++ { ++ name: `scan ${args.metric}`, ++ op: "analytics.scan", ++ attributes: { ++ "analytics.metric": args.metric, ++ "analytics.range_days": rangeDaysOf(args.from, args.to), ++ "analytics.group_by": args.groupBy ?? "none", ++ }, ++ }, ++ async (span) => { ++ const { rows } = await scan(args); ++ span.setAttribute("analytics.row_count", rows.length); ++ return rows; ++ }, ++ ); + } + + function groupTotalsOf(rows: Row[], groupBy: string, unit: MetricDef["unit"]) { +diff --git b/apps/slack-agent/src/app.ts a/apps/slack-agent/src/app.ts +index b66a61c..ac6b07d 100644 +--- b/apps/slack-agent/src/app.ts ++++ a/apps/slack-agent/src/app.ts @@ -1,3 +1,5 @@ -+import "./instrument"; ++import "./instrument.ts"; +import * as Sentry from "@sentry/node"; import { App } from "@slack/bolt"; import type { SayFn, SayStreamFn, SetStatusFn } from "@slack/bolt"; import type { BlockFeedbackButtonsAction } from "@slack/bolt"; -@@ -101,101 +103,133 @@ async function respond({ +@@ -68,6 +70,18 @@ const app = new App({ + // call of its own thread. + const activeRuns = new Map(); + ++// Bolt handles every event inside one long-lived process. Without a fresh ++// isolation scope and trace, each message would join the previous message's ++// trace and inherit its user and conversation id. ++function startMessageSpan( ++ options: Parameters[0], ++ callback: () => Promise, ++): Promise { ++ return Sentry.withIsolationScope(() => ++ Sentry.startNewTrace(() => Sentry.startSpan(options, callback)), ++ ); ++} ++ + async function respond({ + client, + event, +@@ -86,14 +100,36 @@ async function respond({ + channelType: "im" | "channel"; + }) { + const conversationId = event.thread_ts ?? event.ts; ++ const recordContent = channelType !== "im"; + let stream: ReturnType | undefined; + + const run = new AbortController(); const runKey = `${event.channel}:${conversationId}`; activeRuns.set(runKey, run); - try { - await setStatus({ -- status: "is thinking...", -- loading_messages: LOADING_MESSAGES, -- }).catch((error) => console.warn("setStatus failed", error)); -- -- const messages = await loadThread({ client, event, conversationId, text }); -- -- stream = sayStream({ task_display_mode: "timeline" }); -- -- const taskStatus = { -- "tool-call": "in_progress", -- "tool-result": "complete", -- "tool-error": "error", -- } as const; -- -- let reply = ""; -- const found = new Map(); -- let orders: AccountInfo["orders"] = []; -- const failed: string[] = []; -- -- for await (const part of streamAnswer({ -- messages, -- conversationId, -- abortSignal: run.signal, -- }).fullStream) { -- switch (part.type) { -- case "text-delta": -- reply += part.text; -- await stream.append({ markdown_text: part.text }); -- break; -- case "tool-call": -- case "tool-result": -- case "tool-error": -- await stream.append({ -- chunks: [ -- { -- type: "task_update", -- id: part.toolCallId, -- title: TOOL_TITLES[part.toolName] ?? part.toolName, -- status: taskStatus[part.type], -- }, -- ], -- }); -- if (part.type === "tool-result" && !part.dynamic) { -- if (part.toolName === "searchProducts") { -- for (const p of part.output.products) found.set(p.handle, p); -- } else if (part.toolName === "getProduct" && part.output.product) { -- found.set(part.output.product.handle, part.output.product); -- } else if (part.toolName === "getAccountInfo") { -- orders = part.output.orders; -- } -- } -- if (part.type === "tool-error") { -- failed.push(TOOL_TITLES[part.toolName] ?? part.toolName); + // One Slack message is one trace: this span is the root, so the model and + // tool spans from the AI SDK's telemetry nest inside it. -+ await Sentry.startSpan( ++ await startMessageSpan( + { + name: "slack.message", + op: "slack.message", + attributes: { + "gen_ai.conversation.id": conversationId, + "slack.channel": event.channel, ++ "slack.channel_type": channelType, ++ "slack.record_content": recordContent, + }, + }, + async () => { @@ -1555,140 +1449,31 @@ index 0000000..0000000 100644 + + try { + await setStatus({ -+ status: "is thinking...", -+ loading_messages: LOADING_MESSAGES, -+ }).catch((error) => console.warn("setStatus failed", error)); -+ -+ const messages = await loadThread({ -+ client, -+ event, -+ conversationId, -+ text, -+ }); -+ -+ stream = sayStream({ task_display_mode: "timeline" }); -+ -+ const taskStatus = { -+ "tool-call": "in_progress", -+ "tool-result": "complete", -+ "tool-error": "error", -+ } as const; -+ -+ let reply = ""; -+ const found = new Map(); -+ let orders: AccountInfo["orders"] = []; -+ const failed: string[] = []; -+ -+ for await (const part of streamAnswer({ -+ messages, -+ conversationId, -+ abortSignal: run.signal, -+ }).fullStream) { -+ switch (part.type) { -+ case "text-delta": -+ reply += part.text; -+ await stream.append({ markdown_text: part.text }); -+ break; -+ case "tool-call": -+ case "tool-result": -+ case "tool-error": -+ await stream.append({ -+ chunks: [ -+ { -+ type: "task_update", -+ id: part.toolCallId, -+ title: TOOL_TITLES[part.toolName] ?? part.toolName, -+ status: taskStatus[part.type], -+ }, -+ ], -+ }); -+ if (part.type === "tool-result" && !part.dynamic) { -+ if (part.toolName === "searchProducts") { -+ for (const p of part.output.products) found.set(p.handle, p); -+ } else if ( -+ part.toolName === "getProduct" && -+ part.output.product -+ ) { -+ found.set(part.output.product.handle, part.output.product); -+ } else if (part.toolName === "getAccountInfo") { -+ orders = part.output.orders; -+ } -+ } -+ if (part.type === "tool-error") { -+ failed.push(TOOL_TITLES[part.toolName] ?? part.toolName); -+ } -+ break; -+ case "abort": -+ // The user pressed stop. Slack has already closed the streamed -+ // message, so there is nothing left to stop or decorate. -+ return; -+ case "error": -+ throw part.error; -+ default: -+ break; - } -- break; -- case "abort": -- // The user pressed stop. Slack has already closed the streamed -- // message, so there is nothing left to stop or decorate. -- return; -- case "error": -- throw part.error; -- default: -- break; -+ } -+ -+ // A search returns up to six products and the model often picks a few -+ // (for example "under $60"), so show only the ones the reply names. -+ const products = [...found.values()].filter((p) => -+ reply.includes(p.title), -+ ); -+ const named = orders.filter((o) => reply.includes(o.id)); -+ await stream.stop({ -+ blocks: [ -+ ...failureNotice(failed), -+ ...productCarousel(products), -+ ...ordersTable(named), -+ ...FEEDBACK_BLOCK, -+ ], -+ }); + status: "is thinking...", + loading_messages: LOADING_MESSAGES, + }).catch((error) => console.warn("setStatus failed", error)); +@@ -120,6 +156,7 @@ async function respond({ + messages, + conversationId, + abortSignal: run.signal, ++ recordContent, + }).fullStream) { + switch (part.type) { + case "text-delta": +@@ -163,21 +200,24 @@ async function respond({ + await stream.stop({ + blocks: [...failureNotice(failed), ...answerBlocks(outcomes), ...FEEDBACK_BLOCK], + }); +- } catch (error) { +- if (stream) { + } catch (error) { + Sentry.captureException(error); + if (stream) { -+ await stream.stop({ markdown_text: ERROR_REPLY }); -+ } else { -+ await say({ text: ERROR_REPLY, thread_ts: conversationId }); -+ } -+ throw error; -+ } finally { -+ activeRuns.delete(runKey); -+ // Slack's agent messaging experience keeps the status until the app -+ // clears it; the older assistant experience cleared it on the reply. -+ await setStatus("").catch((error) => -+ console.warn("setStatus failed", error), -+ ); - } -- } -- -- // A search returns up to six products and the model often picks a few -- // (for example "under $60"), so show only the ones the reply names. -- const products = [...found.values()].filter((p) => reply.includes(p.title)); -- const named = orders.filter((o) => reply.includes(o.id)); -- await stream.stop({ -- blocks: [ -- ...failureNotice(failed), -- ...productCarousel(products), -- ...ordersTable(named), -- ...FEEDBACK_BLOCK, -- ], -- }); -- } catch (error) { -- if (stream) { -- await stream.stop({ markdown_text: ERROR_REPLY }); -- } else { -- await say({ text: ERROR_REPLY, thread_ts: conversationId }); -- } -- throw error; + await stream.stop({ markdown_text: ERROR_REPLY }); + } else { + await say({ text: ERROR_REPLY, thread_ts: conversationId }); + } + throw error; - } finally { - activeRuns.delete(runKey); - // Slack's agent messaging experience keeps the status until the app @@ -1697,20 +1482,33 @@ index 0000000..0000000 100644 - console.warn("setStatus failed", error), - ); - } ++ } finally { ++ activeRuns.delete(runKey); ++ // Slack's agent messaging experience keeps the status until the app ++ // clears it; the older assistant experience cleared it on the reply. ++ await setStatus("").catch((error) => ++ console.warn("setStatus failed", error), ++ ); ++ } + }, + ); } app.event( -diff --git a/apps/slack-agent/src/instrument.ts b/apps/slack-agent/src/instrument.ts +diff --git b/apps/slack-agent/src/instrument.ts a/apps/slack-agent/src/instrument.ts new file mode 100644 -index 0000000..0000000 +index 0000000..a35ca84 --- /dev/null -+++ b/apps/slack-agent/src/instrument.ts -@@ -0,0 +1,6 @@ ++++ a/apps/slack-agent/src/instrument.ts +@@ -0,0 +1,11 @@ +import * as Sentry from "@sentry/node"; + +Sentry.init({ + dsn: process.env.SENTRY_DSN, ++ release: process.env.SENTRY_RELEASE, ++ environment: process.env.SENTRY_ENVIRONMENT ?? "development", + tracesSampleRate: 1, ++ // Prompts and replies are off by default for this app; src/agent.ts turns ++ // them on per call when the message is in a channel. ++ dataCollection: { genAI: { inputs: false, outputs: false } }, +});