diff --git a/config/osv-scanner.toml b/config/osv-scanner.toml index 754023adf..e010c44fc 100644 --- a/config/osv-scanner.toml +++ b/config/osv-scanner.toml @@ -3,89 +3,6 @@ id = "GO-2026-5932" reason = "False positive — golang.org/x/crypto/openpgp sub-package is never imported by this project; only golang.org/x/crypto/bcrypt is used." - -# --- Accepted risks, re-check by the ignoreUntil date --- -[[ignoredVulns]] -id = "GHSA-2j4c-ffch-9f23" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-5h23-36rv-pm65" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-8jjq-8j9w-m2v6" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-8mcq-6wmr-jrjv" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-c85p-xxjj-2r75" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-fq3v-74gv-27gm" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-fw94-4wwp-w8pw" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-g27h-8qhm-6pff" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-jrfj-fhj2-jjvm" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-jw42-f3rr-4cc3" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-mx22-3794-2vpv" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-r7x5-4969-99p7" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-rxcj-4pj5-74gr" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-wp2g-vpjj-g53r" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - -[[ignoredVulns]] -id = "GHSA-x2q3-8cjh-766f" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): github.com/xuri/excelize/v2 2.11.0 has no tagged release with this fix yet (only unreleased commits). Only used write-only (excelize.NewFile in internal/api/taxonomy_report_handler.go) to build the taxonomy report export; it never opens or parses uploaded spreadsheets. Upgrade to the next tagged excelize release, then remove this entry." - [[ignoredVulns]] id = "GHSA-vfj7-8cjw-p6xm" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): braces 3.0.3 is a dev-only build dependency (ui/, extension/) and no fixed version exists yet. Not shipped in the server binary or UI bundle." - -[[ignoredVulns]] -id = "GHSA-rj75-hqrm-r3gf" -ignoreUntil = 2027-01-08T00:00:00Z -reason = "Accepted risk (2026-10-10): top-level postcss-selector-parser is fixed (7.1.6); remaining 6.1.4 copies are nested under tailwindcss/postcss plugins pinned to ^6 (dev-only CSS tooling). Clears when those tools move to postcss-selector-parser 7." +reason = "braces v3.0.3 is the latest version available and there is no patch. As a devDependency only invoked at build time, it is not exposed to untrusted user input." diff --git a/docs/mcp/mcp-api.md b/docs/mcp/mcp-api.md new file mode 100644 index 000000000..46d85ef4e --- /dev/null +++ b/docs/mcp/mcp-api.md @@ -0,0 +1,74 @@ +# MCP & AI Provider Error Handling + +The LiveReview WebChat endpoint (`/api/v1/chat/send`) has multiple layers of resilience and error handling for the AI layer and the internal MCP server. + +To prevent user confusion when errors occur, the backend intercepts these failures and injects specific UI components into the response: +- **Blockquote Messages**: A stylized error message explaining what happened. +- **Action Cards**: A card with a button (e.g., "Configure AI Provider", "Try Again") directing the user on how to fix it. +- **Suggested Questions**: Helpful fallback questions (e.g., "Where do I get an AI API key?"). +- **Debug Artifacts**: The raw underlying error (e.g., HTTP 502, JSON parsing errors) stored in the debug logs for developers. + +## Error Handling Cases + +To make it easier to debug, we categorize errors into two main areas: **Internal MCP Issues (Our End)** and **LLM Provider Issues (Model Side)**. + +### 1. Internal MCP Issues (Our End) +These occur rarely, usually due to infrastructure or internal server problems. When these happen, we display a clear message indicating the problem is on our end, and provide debug logs that the user can share with us for troubleshooting. + +| Case | Trigger / Condition | Message Prefix | Action Card Title | Suggested Questions | Fallback Behavior | +|------|---------------------|----------------|-------------------|---------------------|-------------------| +| **MCP Server Offline** | Connection to the MCP server fails (e.g. `502 Bad Gateway`). | `> **Something went wrong on our end**` | **MCP Server Offline** (Button: Try Again) | None | Turn aborted. Debug log shows raw network error. | + +### 2. LLM Provider Issues (Model Side) +These are the most common issues. They occur due to provider rate limits, model deprecations, missing API keys, or hallucinated/truncated formatting. + +| Case | Trigger / Condition | Message Prefix | Action Card Title | Suggested Questions | Fallback Behavior | +|------|---------------------|----------------|-------------------|---------------------|-------------------| +| **Auth / Config Issue** | AI Provider API key is invalid or missing (`ErrCategoryAuth`). | `> **Action Required: AI Provider Issue**` | **Configuration Required** (Button: Configure AI Provider) | `DefaultAIErrorSuggestedQuestions` | Turn aborted. | +| **Provider Overloaded** | AI Provider returns 503 or 429 (`ErrCategoryOverload`). | `> **AI Provider is Busy**` | **High Traffic Detected** (Button: Configure AI Provider) | `DefaultAIErrorSuggestedQuestions` | **Graceful Degradation**: If this happens during `classify`, the backend degrades to `product_guidance` mode, attaches the Action Card, and successfully generates a helpful product answer. | +| **Model Deprecated** | The configured AI model was removed (`ErrCategoryDeprecated`). | `> **Model No Longer Available**` | **Model No Longer Available** (Button: Configure AI Provider) | `DefaultAIErrorSuggestedQuestions` | Turn aborted. | +| **Timeout Error** | AI Provider takes too long to respond (`ErrCategoryTimeout`). | `> **Analysis Took Too Long**` | **Timeout Error** (Button: Try Again) | `DefaultAIErrorSuggestedQuestions` | Turn aborted. | +| **Unexpected AI Error** | Any other unclassified LLM API error (`ErrCategoryUnknown`). | `> **Unexpected Error**` | **Unexpected Error** (Button: Try Again) | `DefaultAIErrorSuggestedQuestions` | Turn aborted. | +| **Model Output Error** | The LLM returns truncated or malformed JSON (e.g., `envelope unmarshal failed`). | `> **Model Output Error**` | **Formatting Error** (Button: Try Again) | `DefaultAIErrorSuggestedQuestions` | Turn aborted (often caused by `maxOutputTokens` limits). | +| **No Data Found** | An analytics query successfully completes but returns 0 rows. | (Context-specific text explaining no data) | None | `DefaultNoDataSuggestedQuestions` | Chart rendering is skipped, dynamic text explaining the empty result is returned. | + +## Flow Overview + +1. **Early Failures**: If the MCP connection fails before the agent loop starts, `webchat_handler.go` intercepts it and constructs a `HistoryEntry` containing the `ActionCard` and `DebugArtifacts`. +2. **During the Agent Loop**: If the LLM call fails, `agent.go` categorizes the error (using `aiconnectors.CategorizeLLMError`). + - For most errors, the loop aborts and returns an error template. + - For `classify` overload errors, it intercepts the error, appends the Action Card, and forces the agent to act as a `product_guidance` agent for that turn. +3. **Output Parsing**: If the LLM succeeds but its JSON is malformed (e.g., due to token limits), `analytics.go` returns `Model Output Error` and attaches the Formatting Error action card. +4. **Persistence**: All of these states correctly persist the `action_card` and `debug_artifacts` to the `chat_messages` table via `persistReply`, so that they reload flawlessly if the user refreshes the page. + +## API Response Structure + +The `/api/v1/chat/send` endpoint returns a structured JSON payload (`WebChatResponse`). To support dynamic UI error rendering without hardcoded string matching on the frontend, the backend explicitly sets the `is_error` flag and provides the `action_card` payload. + +```json +{ + "response": "> **Something went wrong on our end**\n> \n> I am having trouble connecting to my internal data tools right now. The technical details have been attached below for troubleshooting. Please try your request again in a few moments.", + "conversationId": 123, + "sessionId": "abc-123", + "is_error": true, + "action_card": { + "title": "MCP Server Offline", + "description": "The internal data server is unreachable (502 Bad Gateway).", + "button_text": "Try Again", + "action_url": "#retry" + }, + "debug_artifacts": { + "raw_llm_error": "MCP Connection Failed: 502 Bad Gateway" + }, + "suggested_questions": [ + { + "category": "Troubleshooting", + "questions": ["Where do I configure my AI provider?"] + } + ] +} +``` + +By passing `is_error: true`, the React frontend (`ChatConversation.tsx`) automatically wraps the response in a red error blockquote and displays the provided `action_card`. + + diff --git a/docs/mcp_error_handling.md b/docs/mcp_error_handling.md new file mode 100644 index 000000000..5675eda5e --- /dev/null +++ b/docs/mcp_error_handling.md @@ -0,0 +1,65 @@ +# MCP LLM Error Handling + +When interacting with multiple AI Providers (Google, OpenAI, Anthropic) via `langchaingo`, LLM calls can fail in messy, unpredictable ways. Providers may return HTTP 503s for overload, 429s for rate limits, or 401s for revoked keys. + +If we return these raw errors directly to the MCP client (the chat UI), the turn crashes and the user sees ugly stack traces (e.g., `googleapi: Error 503`). + +To solve this, we use a **centralized UI fallback mechanism** that traps raw SDK errors and gracefully degrades the UI without disrupting the core ReAct loop. + +## The Architecture + +The error handling logic lives in `internal/mcpagent/provider.go` and consists of two functions: + +### 1. `CategorizeLLMError(err error) LLMErrorCategory` +This function intercepts an error, inspects it for `langchaingo` normalizations, unwraps leaked provider-specific errors (like `*googleapi.Error`), and falls back to string matching. + +It maps the messy error into one of four clean categories: +* `ErrCategoryAuth` (401, 403, 404, invalid keys, revoked models) +* `ErrCategoryOverload` (429, 503, rate limits, high demand) +* `ErrCategoryTimeout` (504, context deadline exceeded) +* `ErrCategoryUnknown` (Prompt parsing errors, network disconnects) + +### 2. `GetLLMFallbackMessage(cat LLMErrorCategory) string` +This function takes a category and returns a beautifully formatted, non-emoji Markdown string to show the user. +* If it returns a non-empty string, **the turn is considered saved**. We show the user this message. +* If it returns `""` (for `ErrCategoryUnknown`), the error is fatal and should be bubbled up. + +## How to use it + +We deliberately **do not** wrap the original `a.provider.Complete(...)` function. We keep the core LLM signatures untouched. Instead, you just "pick up" the error immediately after the LLM call fails. + +### Example (from `agent.go`) + +```go +response, usage, err := a.provider.Complete(ctx, history, tools, completeOpts...) +if err != nil { + // 1. Pick up the error and categorize it + errCategory := CategorizeLLMError(err) + + // 2. Ask for a clean UI message + msg := GetLLMFallbackMessage(errCategory) + + // 3. If there is no clean fallback, bubble up the fatal error + if msg == "" { + return "", history, nil, nil, fmt.Errorf("llm completion failed: %w", err) + } + + // 4. Otherwise, inject the fallback message as an AI response and return a nil error! + // This gracefully completes the turn and renders the message in the UI. + history = append(history, HistoryEntry{ + "role": "assistant", + "content": msg, + "text": msg, + }) + return msg, history, nil, nil, nil +} +``` + +## Adding new Fallback Scenarios + +If you need to handle a new failure mode (e.g. `ContextTooLarge`): +1. Add `ErrCategoryContextSize` to the const block in `provider.go`. +2. Add the detection logic to `CategorizeLLMError`. +3. Add the exact Markdown message you want the user to see to the `switch` block in `GetLLMFallbackMessage`. + +This guarantees every MCP LLM call across the app degrades consistently. diff --git a/extension/livereview/package-lock.json b/extension/livereview/package-lock.json index 37cec2fac..b395ec19e 100644 --- a/extension/livereview/package-lock.json +++ b/extension/livereview/package-lock.json @@ -7,14 +7,11 @@ "": { "name": "livereview", "version": "0.0.15", - "dependencies": { - "shell-quote": "^1.9.0" - }, "devDependencies": { "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/vscode": "^1.70.0", - "@vscode/test-cli": "^0.0.12", + "@vscode/test-cli": "^0.0.15", "@vscode/test-electron": "^2.5.2", "esbuild": "^0.28.1", "eslint": "^9.39.1", @@ -754,10 +751,66 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -775,9 +828,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -1079,27 +1132,43 @@ } }, "node_modules/@vscode/test-cli": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.12.tgz", - "integrity": "sha512-iYN0fDg29+a2Xelle/Y56Xvv7Nc8Thzq4VwpzAF/SIE6918rDicqfsQxV6w1ttr2+SOm+10laGuY9FG2ptEKsQ==", + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.15.tgz", + "integrity": "sha512-nAxk2X79wuXS7aOhyFFhFcCqd7EBUoMesu7ZgsYE/4eFjyBMuyIweVE94BxdKH1RieN8eOz2SIrljrZt6Lk9fQ==", "dev": true, "license": "MIT", "dependencies": { "@types/mocha": "^10.0.10", - "c8": "^10.1.3", - "chokidar": "^3.6.0", - "enhanced-resolve": "^5.18.3", - "glob": "^10.3.10", - "minimatch": "^9.0.3", - "mocha": "^11.7.4", + "c8": "^11.0.0", + "chokidar": "^5.0.0", + "enhanced-resolve": "^5.24.0", + "glob": "^13.0.6", + "minimatch": "^10.2.5", + "mocha": "^11.7.6", "supports-color": "^10.2.2", - "yargs": "^17.7.2" + "yargs": "^18.0.0" }, "bin": { "vscode-test": "out/bin.mjs" }, "engines": { - "node": ">=18" + "node": ">=22" + } + }, + "node_modules/@vscode/test-cli/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "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/@vscode/test-electron": { @@ -1198,20 +1267,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1291,19 +1346,6 @@ "dev": true, "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "5.0.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", @@ -1327,19 +1369,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -1348,9 +1377,9 @@ "license": "ISC" }, "node_modules/c8": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", - "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", "dev": true, "license": "ISC", "dependencies": { @@ -1361,7 +1390,7 @@ "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^7.0.1", + "test-exclude": "^8.0.0", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" @@ -1370,7 +1399,7 @@ "c8": "bin/c8.js" }, "engines": { - "node": ">=18" + "node": "20 || >=22" }, "peerDependencies": { "monocart-coverage-reports": "^2" @@ -1381,6 +1410,96 @@ } } }, + "node_modules/c8/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -1485,28 +1604,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/cli-cursor": { @@ -1539,81 +1649,43 @@ } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=20" } }, "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/color-convert": { @@ -1833,21 +1905,21 @@ "license": "MIT" }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -2289,19 +2361,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2383,21 +2442,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "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, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2460,9 +2504,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", "dev": true, "license": "MIT", "engines": { @@ -2530,37 +2574,37 @@ } }, "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "is-glob": "^4.0.1" + "brace-expansion": "^5.0.8" }, "engines": { - "node": ">= 6" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { @@ -2886,19 +2930,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -3088,16 +3119,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-number-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", @@ -3530,11 +3551,14 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/make-dir": { "version": "4.0.0", @@ -3601,19 +3625,19 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mocha": { - "version": "11.7.5", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", - "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", + "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", "dev": true, "license": "MIT", "dependencies": { @@ -3647,6 +3671,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/mocha/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/mocha/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -3663,6 +3697,67 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/mocha/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/mocha/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mocha/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -3677,6 +3772,34 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/mocha/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -3693,6 +3816,43 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3737,16 +3897,6 @@ "semver": "bin/semver" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -4250,17 +4400,17 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4286,19 +4436,6 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/pidtree": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", @@ -4391,16 +4528,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/reflect.getprototypeof": { @@ -4687,6 +4825,7 @@ "version": "1.12.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4858,18 +4997,17 @@ } }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4901,13 +5039,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5089,9 +5220,9 @@ } }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { @@ -5103,18 +5234,34 @@ } }, "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" + "glob": "^13.0.6", + "minimatch": "^10.2.2" }, "engines": { - "node": ">=18" + "node": "20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "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/tinyglobby": { @@ -5165,19 +5312,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/ts-api-utils": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", @@ -5519,18 +5653,18 @@ "license": "Apache-2.0" }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -5565,13 +5699,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5613,6 +5740,31 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -5624,22 +5776,21 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^8.2.1", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { @@ -5668,49 +5819,14 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { diff --git a/extension/livereview/package.json b/extension/livereview/package.json index 800b331a3..4ef2035a4 100644 --- a/extension/livereview/package.json +++ b/extension/livereview/package.json @@ -133,7 +133,7 @@ "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/vscode": "^1.70.0", - "@vscode/test-cli": "^0.0.12", + "@vscode/test-cli": "^0.0.15", "@vscode/test-electron": "^2.5.2", "esbuild": "^0.28.1", "eslint": "^9.39.1", @@ -161,8 +161,5 @@ "protobufjs": "^7.6.5", "ws": "^8.21.0", "js-yaml@3": "^4.3.2" - }, - "dependencies": { - "shell-quote": "^1.9.0" } } diff --git a/go.mod b/go.mod index a4130e184..34888b769 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/riverqueue/river v0.32.0 github.com/riverqueue/river/riverdriver/riverpgxv5 v0.32.0 - github.com/riverqueue/river/rivertype v0.32.0 github.com/robfig/cron/v3 v3.0.1 github.com/rs/zerolog v1.34.0 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 @@ -59,13 +58,13 @@ require ( github.com/stretchr/testify v1.12.1 github.com/tmc/langchaingo v0.1.14 github.com/wasilibs/go-pgquery v0.0.0-20260728010200-155ebad2880e - github.com/xuri/excelize/v2 v2.11.0 + github.com/xuri/excelize/v2 v2.11.1-0.20261009041834-958cda5363be github.com/yalue/onnxruntime_go v1.32.1 github.com/yuin/goldmark v1.8.5 github.com/zricethezav/gitleaks/v8 v8.30.1 gocloud.dev v0.46.0 - golang.org/x/crypto v0.57.0 - golang.org/x/text v0.42.0 + golang.org/x/crypto v0.58.0 + golang.org/x/text v0.43.0 golang.org/x/time v0.15.0 google.golang.org/api v0.272.0 ) @@ -201,10 +200,11 @@ require ( github.com/pkoukk/tiktoken-go v0.1.6 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/richardlehane/mscfb v1.0.7 // indirect + github.com/richardlehane/mscfb v1.0.8 // indirect github.com/richardlehane/msoleps v1.0.6 // indirect github.com/riverqueue/river/riverdriver v0.32.0 // indirect github.com/riverqueue/river/rivershared v0.32.0 // indirect + github.com/riverqueue/river/rivertype v0.32.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect @@ -236,7 +236,7 @@ require ( github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/xuri/efp v0.0.1 // indirect + github.com/xuri/efp v0.0.2 // indirect github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -255,11 +255,11 @@ require ( golang.org/x/arch v0.25.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/mod v0.41.0 // indirect - golang.org/x/net v0.60.0 // indirect + golang.org/x/net v0.61.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.23.0 // indirect - golang.org/x/sys v0.48.0 // indirect - golang.org/x/tools v0.49.0 // indirect + golang.org/x/sync v0.24.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/tools v0.51.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect diff --git a/go.sum b/go.sum index 32cbabb16..ae5e3ecf2 100644 --- a/go.sum +++ b/go.sum @@ -472,8 +472,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/richardlehane/mscfb v1.0.7 h1:oeoiM0WE79vHwE8RpIYYvIAc8ajTH2mb6UZm55/+EB0= -github.com/richardlehane/mscfb v1.0.7/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= +github.com/richardlehane/mscfb v1.0.8 h1:UXdg61fxF69/X9yMYuRHAWSrGXIul/UAPivAsUXMme8= +github.com/richardlehane/mscfb v1.0.8/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg= github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/riverqueue/river v0.32.0 h1:j15EoFZ4oQWXcCq8NyzWwoi3fdaO8mECTB100NSv9Qw= @@ -596,10 +596,10 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= -github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= -github.com/xuri/excelize/v2 v2.11.0 h1:HxaEFl6sRN2+8J5a8HaKq+0M4FsjBGMnWWtjOCPSG88= -github.com/xuri/excelize/v2 v2.11.0/go.mod h1:jxFLbzaIwGQ5ufFNvYfUOHqXhfPaNmP14KWfmNz2Uak= +github.com/xuri/efp v0.0.2 h1:74yhBh0nfIWLOrN76wtShYVse94pADGYS0/c86jOp8Q= +github.com/xuri/efp v0.0.2/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.11.1-0.20261009041834-958cda5363be h1:ccMVMaLSf+qBGXpYkjo3o3oVsB7+Lmkz4ZRLDrqvQME= +github.com/xuri/excelize/v2 v2.11.1-0.20261009041834-958cda5363be/go.mod h1:d2KG/tW81cIhSeC3S7wENv3qRnOC3MDcrjDicKuZa6U= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -662,10 +662,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= -golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= -golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= +golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -679,8 +677,8 @@ golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= -golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= +golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= +golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -697,8 +695,6 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -717,10 +713,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= -golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= +golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -735,10 +729,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -760,10 +752,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -774,10 +764,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= -golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= @@ -807,8 +795,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI= +golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/aiconnectors/errors.go b/internal/aiconnectors/errors.go new file mode 100644 index 000000000..74da658ff --- /dev/null +++ b/internal/aiconnectors/errors.go @@ -0,0 +1,90 @@ +package aiconnectors + +import ( + "context" + "errors" + "net" + "strings" + + "github.com/tmc/langchaingo/llms" +) + +type LLMErrorCategory string + +const ( + ErrCategoryAuth LLMErrorCategory = "auth_failed" + ErrCategoryOverload LLMErrorCategory = "overloaded" + ErrCategoryTimeout LLMErrorCategory = "timeout" + ErrCategoryDeprecated LLMErrorCategory = "model_deprecated" + ErrCategoryUnknown LLMErrorCategory = "unknown" +) + +// CategorizeLLMError maps raw SDK errors into stable UI fallback categories. +func CategorizeLLMError(err error) LLMErrorCategory { + if err == nil { + return ErrCategoryUnknown + } + + // 1. langchaingo normalized errors. + var e *llms.Error + if errors.As(err, &e) { + switch e.Code { + case llms.ErrCodeAuthentication: + return ErrCategoryAuth + case llms.ErrCodeRateLimit, llms.ErrCodeQuotaExceeded, llms.ErrCodeProviderUnavailable: + return ErrCategoryOverload + case llms.ErrCodeTimeout, llms.ErrCodeCanceled: + return ErrCategoryTimeout + case llms.ErrCodeResourceNotFound: + if strings.Contains(strings.ToLower(e.Message), "model") { + return ErrCategoryDeprecated + } + return ErrCategoryUnknown + } + } + + // 2. Context and network timeouts. + if errors.Is(err, context.DeadlineExceeded) { + return ErrCategoryTimeout + } + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + return ErrCategoryTimeout + } + + // 3. Raw HTTP status codes from unwrapped provider clients. + var code int + var hscErr interface{ HTTPStatusCode() int } + var scErr interface{ StatusCode() int } + if errors.As(err, &hscErr) { + code = hscErr.HTTPStatusCode() + } else if errors.As(err, &scErr) { + code = scErr.StatusCode() + } + + switch code { + case 401, 403: + return ErrCategoryAuth + case 429, 500, 502, 503, 504: + return ErrCategoryOverload + } + + // 4. String fallback for untyped provider errors. + msg := strings.ToLower(err.Error()) + switch { + case strings.Contains(msg, "deprecated"), strings.Contains(msg, "model not found"), + strings.Contains(msg, "model_not_found"), strings.Contains(msg, "no such model"), + strings.Contains(msg, "no longer available"): + return ErrCategoryDeprecated + case strings.Contains(msg, "rate limit"), strings.Contains(msg, "too many requests"), + strings.Contains(msg, "service unavailable"), strings.Contains(msg, "bad gateway"), + strings.Contains(msg, "high demand"), strings.Contains(msg, "quota"): + return ErrCategoryOverload + case strings.Contains(msg, "unauthorized"), strings.Contains(msg, "forbidden"), + strings.Contains(msg, "api key"), strings.Contains(msg, "x-api-key"), + strings.Contains(msg, "permission denied"), strings.Contains(msg, "do not have access"): + return ErrCategoryAuth + } + + return ErrCategoryUnknown +} diff --git a/internal/aiconnectors/errors_test.go b/internal/aiconnectors/errors_test.go new file mode 100644 index 000000000..9e1d73f84 --- /dev/null +++ b/internal/aiconnectors/errors_test.go @@ -0,0 +1,103 @@ +package aiconnectors + +import ( + "context" + "errors" + "fmt" + "net/http" + "testing" + + "github.com/tmc/langchaingo/llms" +) + +// mockHTTPError mocks an error that provides a StatusCode() method. +type mockHTTPError struct { + code int + msg string +} + +func (e *mockHTTPError) Error() string { + return e.msg +} + +func (e *mockHTTPError) StatusCode() int { + return e.code +} + +func TestCategorizeLLMError(t *testing.T) { + tests := []struct { + name string + err error + want LLMErrorCategory + }{ + { + name: "Nil error", + err: nil, + want: ErrCategoryUnknown, + }, + { + name: "Langchain Auth Error", + err: &llms.Error{Code: llms.ErrCodeAuthentication}, + want: ErrCategoryAuth, + }, + { + name: "Langchain Rate Limit Error", + err: &llms.Error{Code: llms.ErrCodeRateLimit}, + want: ErrCategoryOverload, + }, + { + name: "HTTP 401 via StatusCode()", + err: &mockHTTPError{code: http.StatusUnauthorized, msg: "unauthorized"}, + want: ErrCategoryAuth, + }, + { + name: "HTTP 503 via StatusCode()", + err: &mockHTTPError{code: http.StatusServiceUnavailable, msg: "service unavailable"}, + want: ErrCategoryOverload, + }, + { + name: "Timeout: context deadline", + err: fmt.Errorf("operation failed: %w", context.DeadlineExceeded), + want: ErrCategoryTimeout, + }, + { + name: "String Match: high demand", + err: errors.New("googleapi: Error 503: This model is currently experiencing high demand."), + want: ErrCategoryOverload, + }, + { + name: "String Match: error 429", + err: errors.New("provider returned error 429 too many requests"), + want: ErrCategoryOverload, + }, + { + name: "String Match: model not found", + err: errors.New("model not found or deprecated"), + want: ErrCategoryDeprecated, + }, + { + name: "HTTP 404 via StatusCode()", + err: &mockHTTPError{code: http.StatusNotFound, msg: "resource not found"}, + want: ErrCategoryUnknown, + }, + { + name: "String Match: error 502", + err: errors.New("googleapi: error 502: bad gateway"), + want: ErrCategoryOverload, + }, + { + name: "Unknown Error", + err: errors.New("something went completely wrong"), + want: ErrCategoryUnknown, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CategorizeLLMError(tt.err) + if got != tt.want { + t.Errorf("CategorizeLLMError() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/api/chat_conversations_handler.go b/internal/api/chat_conversations_handler.go index 70fc99d7a..b33daff9a 100644 --- a/internal/api/chat_conversations_handler.go +++ b/internal/api/chat_conversations_handler.go @@ -27,13 +27,14 @@ type ChatConversationSummary struct { // ChatMessageOut is one persisted message, with any charts and file exports // it produced. type ChatMessageOut struct { - ID int64 `json:"id"` - Role string `json:"role"` - Content string `json:"content"` - Charts []WebChatChart `json:"charts,omitempty"` - Files []WebChatFile `json:"files,omitempty"` + ID int64 `json:"id"` + Role string `json:"role"` + Content string `json:"content"` + Charts []WebChatChart `json:"charts,omitempty"` + Files []WebChatFile `json:"files,omitempty"` SuggestedQuestions []mcpagent.SuggestedQuestionCategory `json:"suggested_questions,omitempty"` - DebugArtifacts json.RawMessage `json:"debug_artifacts,omitempty"` + DebugArtifacts json.RawMessage `json:"debug_artifacts,omitempty"` + ActionCard *mcpagent.ActionCard `json:"action_card,omitempty"` } // ChatConversationDetail is a full conversation with its message history, for @@ -200,21 +201,36 @@ func (s *Server) GetConversation(c echo.Context) error { Rows: f.Rows, }) } - var suggestedQuestions []mcpagent.SuggestedQuestionCategory + var actionCards []mcpagent.ActionCard + var suggestedQuestionsList [][]mcpagent.SuggestedQuestionCategory for _, entry := range m.RawHistoryEntries { - if sq, ok := entry["suggested_questions"].([]mcpagent.SuggestedQuestionCategory); ok && len(sq) > 0 { - suggestedQuestions = sq - break - } else if rawSq, ok := entry["suggested_questions"]; ok && rawSq != nil { - if b, err := json.Marshal(rawSq); err == nil { - if err := json.Unmarshal(b, &suggestedQuestions); err != nil { - log.Warn().Err(err).Msg("failed to unmarshal raw suggested_questions") - } else if len(suggestedQuestions) > 0 { - break + if ac, ok := entry["action_card"]; ok && ac != nil { + if b, err := json.Marshal(ac); err == nil { + var card mcpagent.ActionCard + if err := json.Unmarshal(b, &card); err == nil { + actionCards = append(actionCards, card) + } + } + } + if sq, ok := entry["suggested_questions"]; ok && sq != nil { + if b, err := json.Marshal(sq); err == nil { + var cats []mcpagent.SuggestedQuestionCategory + if err := json.Unmarshal(b, &cats); err == nil { + suggestedQuestionsList = append(suggestedQuestionsList, cats) } } } } + + var actionCard *mcpagent.ActionCard + if n := len(actionCards); n > 0 { + last := actionCards[n-1] + actionCard = &last + } + var suggestedQuestions []mcpagent.SuggestedQuestionCategory + if n := len(suggestedQuestionsList); n > 0 { + suggestedQuestions = suggestedQuestionsList[n-1] + } out.Messages = append(out.Messages, ChatMessageOut{ ID: m.ID, Role: m.Role, @@ -223,6 +239,7 @@ func (s *Server) GetConversation(c echo.Context) error { Files: files, SuggestedQuestions: suggestedQuestions, DebugArtifacts: m.DebugArtifacts, + ActionCard: actionCard, }) } return c.JSON(http.StatusOK, out) diff --git a/internal/api/webchat_handler.go b/internal/api/webchat_handler.go index 6c873d2d0..c4255b36c 100644 --- a/internal/api/webchat_handler.go +++ b/internal/api/webchat_handler.go @@ -52,13 +52,17 @@ type WebChatChart struct { } type WebChatResponse struct { - Response string `json:"response"` - Charts []WebChatChart `json:"charts,omitempty"` - Files []WebChatFile `json:"files,omitempty"` + Response string `json:"response"` + Charts []WebChatChart `json:"charts,omitempty"` + Files []WebChatFile `json:"files,omitempty"` SuggestedQuestions []mcpagent.SuggestedQuestionCategory `json:"suggested_questions,omitempty"` - DebugArtifacts json.RawMessage `json:"debug_artifacts,omitempty"` - SessionID string `json:"sessionId,omitempty"` - ConversationID int64 `json:"conversationId"` + DebugArtifacts json.RawMessage `json:"debug_artifacts,omitempty"` + SessionID string `json:"sessionId,omitempty"` + ConversationID int64 `json:"conversationId"` + IsError bool `json:"is_error,omitempty"` + // ActionCard provides structured data for the frontend to render an action card + // (e.g. "Configuration Required") without hardcoding string matching in the UI. + ActionCard *mcpagent.ActionCard `json:"action_card,omitempty"` } // analyticsRoleFor maps permissions onto the SQL catalog's roles. Super admins @@ -197,9 +201,31 @@ func (s *Server) HandleWebChat(c echo.Context) error { mcpSession, err := mcpagent.ConnectMCP(ctx, mcpURL, mcpHeaders) if err != nil { log.Error().Err(err).Str("url", mcpURL).Msg("WebChat: failed to connect to MCP server") - errText := fmt.Sprintf("Internal AI Service Error: Unable to connect to MCP tools (%s).", err.Error()) - persistReply(errText, nil, nil, nil, "", nil) - return c.JSON(http.StatusServiceUnavailable, map[string]string{"error": errText}) + userFriendlyErr, card, debugArt := mcpagent.NewMCPOfflineError(err, mcpURL) + debugArtJSON, marshalErr := json.Marshal(debugArt) + if marshalErr != nil { + log.Warn().Err(marshalErr).Msg("WebChat: failed to marshal debug artifacts") + } + + entry := mcpagent.HistoryEntry{ + "role": "assistant", + "content": userFriendlyErr, + "text": userFriendlyErr, + "action_card": &card, + "debug_artifacts": debugArt, + "is_error": true, + } + persistReply(userFriendlyErr, nil, nil, []mcpagent.HistoryEntry{entry}, string(debugArtJSON), debugArt) + resp := WebChatResponse{ + Response: userFriendlyErr, + SessionID: sessionID, + ConversationID: convID, + IsError: true, + ActionCard: &card, + DebugArtifacts: debugArtJSON, + } + // Return 200 OK so the frontend renders the action card and debug logs instead of a raw network error + return c.JSON(http.StatusOK, resp) } if pc.CurrentOrg != nil && pc.CurrentOrg.Name != "" { @@ -221,9 +247,14 @@ func (s *Server) HandleWebChat(c echo.Context) error { responseText, updatedHistory, artifacts, debugArt, err := agent.RunTurnWithArtifacts(ctx, history, req.Message, sessionID, "livi") if err != nil { log.Error().Err(err).Msg("WebChat: agent loop failed") - errText := fmt.Sprintf("Agent loop failed: %s", err.Error()) - persistReply(errText, nil, nil, nil, "", nil) - return c.JSON(http.StatusInternalServerError, map[string]string{"error": errText}) + userFriendlyErr := "> **AI Processing Error**\n> \n> I encountered an unexpected error while processing your request. Please try asking again." + persistReply(userFriendlyErr, nil, nil, nil, "", nil) + resp := WebChatResponse{ + Response: userFriendlyErr, + SessionID: sessionID, + ConversationID: convID, + } + return c.JSON(http.StatusOK, resp) } // Whatever RunTurnWithArtifacts appended on top of the history we fed it // (possibly including a freshly swapped-in system prompt entry - see @@ -240,6 +271,33 @@ func (s *Server) HandleWebChat(c echo.Context) error { } for _, entry := range turnEntries { + if rawCard, ok := entry["action_card"]; ok && rawCard != nil && resp.ActionCard == nil { + if acPtr, ok := rawCard.(*mcpagent.ActionCard); ok { + acCopy := *acPtr + resp.ActionCard = &acCopy + } else if acVal, ok := rawCard.(mcpagent.ActionCard); ok { + resp.ActionCard = &acVal + } else { + if b, err := json.Marshal(rawCard); err == nil { + var ac mcpagent.ActionCard + if err := json.Unmarshal(b, &ac); err == nil { + resp.ActionCard = &ac + } else { + log.Error().Err(err).Msg("WebChat: failed to unmarshal action_card fallback") + } + } else { + log.Error().Err(err).Msg("WebChat: failed to marshal rawCard fallback") + } + } + } + if isError, ok := entry["is_error"].(bool); ok && isError { + resp.IsError = true + } + if rawDebug, ok := entry["debug_artifacts"]; ok && rawDebug != nil && resp.DebugArtifacts == nil { + if b, err := json.Marshal(rawDebug); err == nil { + resp.DebugArtifacts = b + } + } if sq, ok := entry["suggested_questions"].([]mcpagent.SuggestedQuestionCategory); ok && len(sq) > 0 { resp.SuggestedQuestions = sq break @@ -253,6 +311,10 @@ func (s *Server) HandleWebChat(c echo.Context) error { } } } + // If we have an action_card but no suggested questions were attached, add defaults. + if resp.ActionCard != nil && len(resp.SuggestedQuestions) == 0 { + resp.SuggestedQuestions = mcpagent.DefaultAIErrorSuggestedQuestions + } if vlrender.HasVegaLiteSpec(responseText) { charts, cleanText, err := extractChartsFromVega(responseText) @@ -324,6 +386,7 @@ func (s *Server) HandleWebChat(c echo.Context) error { resp.Files = append(resp.Files, chatFileFromArtifact(art, fileIDs[i])) } + // log.Info().Interface("finalResp", resp).Msg("WebChat: returning response to client") return c.JSON(http.StatusOK, resp) } @@ -356,13 +419,18 @@ func titleFromFirstMessage(message string) string { // (agent.go:148-166), so replaying persisted history without it is safe, and // simpler than tracking which prompt variant was live on a given turn. func persistAssistantMessage(ctx context.Context, chatStore *storagechat.Store, convID int64, userText, assistantText string, turnEntries []mcpagent.HistoryEntry, charts []WebChatChart, artifacts []mcpagent.Artifact, rawLLMOutput string, debugArtifacts json.RawMessage) ([]int64, error) { - assistantEntries := []mcpagent.HistoryEntry{{"role": "assistant", "content": assistantText}} + assistantEntries := turnEntries + hasUser := false for i, e := range turnEntries { if role, _ := e["role"].(string); role == "user" { assistantEntries = turnEntries[i+1:] + hasUser = true break } } + if !hasUser && len(assistantEntries) == 0 { + assistantEntries = []mcpagent.HistoryEntry{{"role": "assistant", "content": assistantText}} + } chartInputs := make([]storagechat.ChartInput, 0, len(charts)) for _, ch := range charts { diff --git a/internal/mcpagent/agent.go b/internal/mcpagent/agent.go index c55b7461f..6ca4545bd 100644 --- a/internal/mcpagent/agent.go +++ b/internal/mcpagent/agent.go @@ -3,17 +3,16 @@ package mcpagent import ( "context" "encoding/json" - "errors" "fmt" "strings" "time" + "github.com/livereview/internal/aiconnectors" "github.com/livereview/internal/docindex" "github.com/livereview/internal/logging" "github.com/livereview/internal/vlrender" "github.com/rs/zerolog/log" "github.com/tmc/langchaingo/llms" - "google.golang.org/api/googleapi" ) const ( @@ -153,6 +152,8 @@ func (a *Agent) RunTurnWithArtifacts(ctx context.Context, history []HistoryEntry } log.Warn().Str("failure_reason", schemaIndexFailureReason()).Msg("schema index not ready; refusing the turn before any LLM call") clog.FinalResponse(msg) + history = append(history, HistoryEntry{"role": "user", "content": userText}) + history = append(history, HistoryEntry{"role": "assistant", "content": msg, "text": msg}) return msg, history, nil, nil, nil } @@ -162,8 +163,10 @@ func (a *Agent) RunTurnWithArtifacts(ctx context.Context, history []HistoryEntry if a.analyticsEnabled() { res, err := a.classify(ctx, history, userText, clog) shape := res.Shape + var classifyErr error if err != nil { - log.Warn().Err(err).Msg("call #0 classify failed; degrading to product_guidance for this turn") + log.Warn().Err(err).Str("category", string(aiconnectors.CategorizeLLMError(err))).Msg("call #0 classify failed; degrading to product_guidance for this turn") + classifyErr = err shape = shapeProductGuidance } @@ -179,7 +182,10 @@ func (a *Agent) RunTurnWithArtifacts(ctx context.Context, history []HistoryEntry entry := HistoryEntry{"role": "assistant", "content": text, "text": text} if text == NoDataAnalyticsResponseText { entry["suggested_questions"] = DefaultNoDataSuggestedQuestions + } else { + InjectErrorState(entry, text, debugArt) } + history = append(history, entry) return text, history, artifacts, debugArt, nil } @@ -240,7 +246,53 @@ func (a *Agent) RunTurnWithArtifacts(ctx context.Context, history []HistoryEntry } clog.BranchSelected(string(shape), len(systemPrompt), len(tools)) - return a.runStepLoop(ctx, history, userText, systemPrompt, tools, callNumber, jsonMode, planRetried, "", clog) + text, history, artifacts, debugArt, err := a.runStepLoop(ctx, history, userText, systemPrompt, tools, callNumber, jsonMode, planRetried, "", clog) + + if classifyErr != nil && err == nil { + category := aiconnectors.CategorizeLLMError(classifyErr) + if tpl, ok := LookupErrorTemplate(category); ok { + if len(history) > 0 { + lastIdx := len(history) - 1 + lastEntry := history[lastIdx] + if lastEntry == nil { + lastEntry = make(HistoryEntry) + history[lastIdx] = lastEntry + } + fallbackText, _ := lastEntry["text"].(string) + + // 1. Create the error entry with the Action Card and Debug Logs + errorEntry := HistoryEntry{ + "role": "assistant", + "text": tpl.Message, + "content": tpl.Message, + } + card := tpl.ActionCard + errorEntry["action_card"] = &card + + if debugArt == nil { + debugArt = &DebugArtifacts{} + } + debugArt.RawLLMError = classifyErr.Error() + errorEntry["debug_artifacts"] = debugArt + + // 2. Modify the last entry (the fallback guidance) + newFallbackText := "However, here is some general product guidance:\n\n---\n\n" + fallbackText + lastEntry["text"] = newFallbackText + lastEntry["content"] = newFallbackText + + // 3. Insert the error entry before the fallback guidance entry + var newHistory []HistoryEntry + newHistory = append(newHistory, history[:lastIdx]...) + newHistory = append(newHistory, errorEntry) + newHistory = append(newHistory, lastEntry) + history = newHistory + + text = tpl.Message + "\n\n" + newFallbackText + } + } + } + + return text, history, artifacts, debugArt, err } // Non-analytics path (plain tool-only agent). @@ -295,27 +347,40 @@ func (a *Agent) runStepLoop( if jsonMode { completeOpts = append(completeOpts, llms.WithJSONMode()) } + response, usage, err := a.provider.Complete(ctx, history, tools, completeOpts...) aiElapsed := time.Since(aiStart) if err != nil { - log.Error().Err(err).Int("step", step).Msg("LLM completion failed") + errCategory := aiconnectors.CategorizeLLMError(err) + log.Error().Err(err).Int("step", step).Str("category", string(errCategory)).Msg("LLM completion failed") clog.AIError(callNumber, step, aiElapsed, err) - if isProviderAuthOrModelError(err) { - msg := fmt.Sprintf("> ⚠️ **Action Required: AI Provider Issue**\n> \n> The existing **%s**'s key seems expired/revoked or the model is no longer available.\n\nPlease configure a valid provider to continue: [btn:Configure AI Provider](/settings#ai)", a.provider.Describe()) - clog.FinalResponse(msg + " (stopped after provider auth/model error)") - - history = append(history, HistoryEntry{ - "role": "assistant", - "content": msg, - "text": msg, - "suggested_questions": DefaultAIErrorSuggestedQuestions, - }) - - return msg, history, nil, nil, nil + msg := "" + var card *ActionCard + if tpl, ok := LookupErrorTemplate(errCategory); ok { + msg = tpl.Message + card = &tpl.ActionCard + } + if msg == "" { + return "", history, nil, nil, fmt.Errorf("llm completion step %d: %w", step, err) } - return "", history, nil, nil, fmt.Errorf("llm completion step %d: %w", step, err) + clog.FinalResponse(msg + " (stopped after " + string(errCategory) + ")") + entry := HistoryEntry{ + "role": "assistant", + "content": msg, + "text": msg, + "suggested_questions": DefaultAIErrorSuggestedQuestions, + } + var debugArt *DebugArtifacts + if card != nil { + entry["action_card"] = card + debugArt = &DebugArtifacts{RawLLMError: err.Error()} + entry["debug_artifacts"] = debugArt + } + + history = append(history, entry) + return msg, history, nil, debugArt, nil } log.Debug().Int("step", step).Int("response_len", len(response)).Msg("LLM call succeeded") clog.AIResponse(callNumber, step, aiElapsed, usage.InputTokens, usage.OutputTokens, response) @@ -593,63 +658,12 @@ func (a *Agent) interpretUserMessage(clog *logging.ChatTurnLogger, userText stri return "", err } msg := fmt.Sprintf( - "User query: %s\nOrg context: org_id = %d (%s)\n\n--- dbctx schema context ---\n%s\n\n--- available chart types ---\n%s", + "User query: %s\nOrg context: org_id = %d (%s)\n\n--- dbctx schema context ---\n%s\n\n--- available chart types ---\n%s\n\nNow, generate the JSON object containing the interpretations as requested. Do NOT output any markdown, reasoning, or text outside of the JSON object.", userText, orgID, orgName, tableText, a.chartTypes, ) return msg, nil } -// isProviderAuthOrModelError reports whether an LLM completion error is caused -// by a revoked/expired AI provider key or a deprecated/unavailable model. -func isProviderAuthOrModelError(err error) bool { - if err == nil { - return false - } - - // 1. Check langchaingo standardized errors - var llmsErr *llms.Error - if errors.As(err, &llmsErr) { - if llmsErr.Code == llms.ErrCodeAuthentication || llmsErr.Code == llms.ErrCodeResourceNotFound { - return true - } - } - - // 2. Check Google API typed errors - var gErr *googleapi.Error - if errors.As(err, &gErr) { - if gErr.Code == 401 || gErr.Code == 403 || gErr.Code == 404 { - return true - } - } - - // 3. Check generic interface methods for HTTP status codes (used by OpenAI client wrapper, etc.) - var scErr interface{ StatusCode() int } - if errors.As(err, &scErr) { - code := scErr.StatusCode() - if code == 401 || code == 403 || code == 404 { - return true - } - } - - var hscErr interface{ HTTPStatusCode() int } - if errors.As(err, &hscErr) { - code := hscErr.HTTPStatusCode() - if code == 401 || code == 403 || code == 404 { - return true - } - } - - // 4. Fallback for non-HTTP API responses indicating model unavailability - msg := strings.ToLower(err.Error()) - return strings.Contains(msg, "deprecated") || - strings.Contains(msg, "model not found") || - strings.Contains(msg, "status code: 401") || - strings.Contains(msg, "status code: 403") || - strings.Contains(msg, "status code: 404") || - strings.Contains(msg, "unauthorized") || - strings.Contains(msg, "forbidden") -} - // isAuthError reports whether a tool result signals an expired/invalid // session token or missing auth - the exact messages LiveReview's own auth // middleware returns (internal/api/auth/middleware.go). Retrying with a diff --git a/internal/mcpagent/analytics.go b/internal/mcpagent/analytics.go index ee5bb4392..8936d5e12 100644 --- a/internal/mcpagent/analytics.go +++ b/internal/mcpagent/analytics.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/livereview/internal/aiconnectors" "github.com/livereview/internal/chatstats" "github.com/livereview/internal/livisql" "github.com/livereview/internal/logging" @@ -1569,12 +1570,27 @@ type interpretEnvelope struct { func parseInterpretations(text string) ([]Interpretation, bool) { body := strings.TrimSpace(vlrender.ExtractJSONBlock(text)) if body == "" { + log.Warn().Msg("parseInterpretations: ExtractJSONBlock returned empty string") return nil, false } var env interpretEnvelope - if err := json.Unmarshal([]byte(body), &env); err == nil && len(env.Interpretations) > 0 { - return normalizeInterpretations(env.Interpretations) + if err := json.Unmarshal([]byte(body), &env); err == nil { + log.Debug().Int("interpretations_count", len(env.Interpretations)).Msg("parseInterpretations: envelope parsed") + if len(env.Interpretations) > 0 { + // Log first entry for debugging. + first := env.Interpretations[0] + log.Debug(). + Str("name", first.Name). + Str("chart_type", first.ChartType). + Int("sql_len", len(strings.TrimSpace(first.SQL))). + Msg("parseInterpretations: first interpretation") + return normalizeInterpretations(env.Interpretations) + } + log.Warn().Str("body_prefix", truncateContent(body, 300)).Msg("parseInterpretations: envelope parsed but interpretations array is empty") + return nil, false + } else { + log.Warn().Err(err).Str("body_prefix", truncateContent(body, 300)).Msg("parseInterpretations: envelope unmarshal failed") } // Bare array fallback. @@ -1590,8 +1606,9 @@ func parseInterpretations(text string) ([]Interpretation, bool) { func normalizeInterpretations(in []Interpretation) ([]Interpretation, bool) { out := make([]Interpretation, 0, len(in)) - for _, interp := range in { + for i, interp := range in { if strings.TrimSpace(interp.SQL) == "" { + log.Warn().Int("index", i).Str("name", interp.Name).Str("chart_type", interp.ChartType).Msg("normalizeInterpretations: skipping interpretation with empty sql") continue } if strings.TrimSpace(interp.ChartType) == "" { @@ -1604,6 +1621,7 @@ func normalizeInterpretations(in []Interpretation) ([]Interpretation, bool) { if strings.TrimSpace(interp.Title) == "" { interp.Title = interp.ChartType + " chart" } + log.Debug().Int("index", i).Str("name", interp.Name).Str("chart_type", interp.ChartType).Int("sql_len", len(interp.SQL)).Msg("normalizeInterpretations: accepted") out = append(out, interp) if len(out) >= maxInterpretations { break @@ -1650,25 +1668,29 @@ func (a *Agent) runMultiInterpret( debug.FullRequest = system + "\n---\n" + userMsg raw, err := a.completeOnce(ctx, clog, 2, "interpret", "multi", 1, system, userMsg) if err != nil { + debug.RawLLMError = err.Error() log.Error().Err(err).Msg("multi-interpret LLM call failed") - if isProviderAuthOrModelError(err) { - msg := fmt.Sprintf("> ⚠️ **Action Required: AI Provider Issue**\n> \n> The existing **%s**'s key seems expired/revoked or the model is no longer available.\n\nPlease configure a valid provider to continue: [btn:Configure AI Provider](/settings#ai)", a.provider.Describe()) - clog.FinalResponse(msg + " (stopped after provider auth/model error)") - return msg, nil, debug, nil + cat := aiconnectors.CategorizeLLMError(err) + msg := "" + if tpl, ok := LookupErrorTemplate(cat); ok { + msg = tpl.Message } - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - // Intentionally return nil error here so the agent caller treats this turn as a success - // and persists the friendly timeout message to the database, rather than throwing it away. - return "The analysis took longer than expected. Please try asking a narrower or more specific question.", nil, debug, nil //nolint:nilerr + if msg != "" { + clog.FinalResponse(msg + " (stopped after " + string(cat) + ")") + return msg, nil, debug, nil } - return "I had trouble understanding that question. Please try rephrasing.", nil, debug, nil //nolint:nilerr + + log.Warn().Err(err).Str("category", string(cat)).Msg("multi-interpret LLM call failed; no known fallback") + return "I wasn't able to generate an answer this time. Please try rephrasing your question.", nil, debug, nil //nolint:nilerr } debug.LLMRawResponse = raw interps, ok := parseInterpretations(raw) if !ok { log.Warn().Str("raw_preview", truncateContent(raw, 200)).Msg("could not parse interpretations from LLM response") - return "I could not produce a valid analysis plan. Please try rephrasing.", nil, debug, nil + debug.RawLLMError = "Failed to parse LLM output as JSON (often caused by max token limits). Raw output:\n" + raw + formatMsg := "> **Model Output Error**\n> \n> The AI returned data in an unexpected format. Please try rephrasing your question." + return formatMsg, nil, debug, nil } debug.Interpretations = interps diff --git a/internal/mcpagent/error_handling.go b/internal/mcpagent/error_handling.go new file mode 100644 index 000000000..c3a2fdda8 --- /dev/null +++ b/internal/mcpagent/error_handling.go @@ -0,0 +1,124 @@ +package mcpagent + +import ( + "fmt" + "strings" + + "github.com/livereview/internal/aiconnectors" +) + +// LLMErrorTemplate defines the UI representation of an LLM error, +// including both the chat message text and the Action Card to render. +type LLMErrorTemplate struct { + Message string + ActionCard ActionCard +} + +// LLMErrorTemplates stores the fallback configurations for various LLM errors. +// This allows the frontend to automatically render the correct box without +// hardcoded display logic. +var LLMErrorTemplates = map[aiconnectors.LLMErrorCategory]LLMErrorTemplate{ + aiconnectors.ErrCategoryAuth: { + Message: "> **Action Required: AI Provider Issue**\n> \n> The AI Provider's API key is invalid or the model is missing. Please configure a valid provider in settings to continue.", + ActionCard: ActionCard{ + Title: "Configuration Required", + Description: "Please edit the current AI provider configuration to continue.", + ButtonText: "Configure AI Provider", + ActionURL: "/ai", + }, + }, + aiconnectors.ErrCategoryOverload: { + Message: "> **AI Provider is Busy**\n> \n> The selected model is currently experiencing high traffic. If you continue to see this issue, please edit your configuration to change to a different model or provider.", + ActionCard: ActionCard{ + Title: "High Traffic Detected", + Description: "Please select a different model in settings.", + ButtonText: "Configure AI Provider", + ActionURL: "/ai", + }, + }, + aiconnectors.ErrCategoryDeprecated: { + Message: "> **Model No Longer Available**\n> \n> The selected AI model has been deprecated or removed by the provider. Please update your AI provider configuration to use a different model.", + ActionCard: ActionCard{ + Title: "Model No Longer Available", + Description: "The selected model has been deprecated. Please update your AI provider configuration.", + ButtonText: "Configure AI Provider", + ActionURL: "/ai", + }, + }, + aiconnectors.ErrCategoryTimeout: { + Message: "> **Analysis Took Too Long**\n> \n> The AI took too long to generate your response and timed out. Try asking a narrower or more specific question.", + ActionCard: ActionCard{ + Title: "Timeout Error", + Description: "The request took too long to complete.", + ButtonText: "Try Again", + ActionURL: "#retry", + }, + }, + aiconnectors.ErrCategoryUnknown: { + Message: "> **Unexpected Error**\n> \n> An unexpected error occurred while communicating with the AI provider. The technical details have been attached below for troubleshooting.", + ActionCard: ActionCard{ + Title: "Unexpected Error", + Description: "An unknown error occurred during processing.", + ButtonText: "Try Again", + ActionURL: "#retry", + }, + }, +} + +const ModelOutputErrorText = "Model Output Error" + +// LookupErrorTemplate returns the error template for a given category. +func LookupErrorTemplate(category aiconnectors.LLMErrorCategory) (LLMErrorTemplate, bool) { + tpl, ok := LLMErrorTemplates[category] + return tpl, ok +} + +// MatchErrorTemplateByText returns the error template matching the provided response text. +func MatchErrorTemplateByText(text string) (LLMErrorTemplate, bool) { + for _, tpl := range LLMErrorTemplates { + if strings.HasPrefix(text, tpl.Message) { + return tpl, true + } + } + return LLMErrorTemplate{}, false +} + +// NewMCPOfflineError generates the error response components when the MCP server is unreachable. +func NewMCPOfflineError(err error, mcpURL string) (string, ActionCard, *DebugArtifacts) { + msg := "> **Something went wrong on our end**\n> \n> I am having trouble connecting to my internal data tools right now. The technical details have been attached below for troubleshooting. Please try your request again in a few moments." + + debugLog := fmt.Sprintf("MCP Connection Failed: %s\nURL: %s", err.Error(), mcpURL) + debugArt := &DebugArtifacts{RawLLMError: debugLog} + + card := ActionCard{ + Title: "MCP Server Offline", + Description: "The internal data server is unreachable (502 Bad Gateway).", + ButtonText: "Try Again", + ActionURL: "#retry", + } + + return msg, card, debugArt +} + +// InjectErrorState parses the text and populates the HistoryEntry with the appropriate ActionCard, +// DebugArtifacts, SuggestedQuestions, and sets is_error to true if it matches a known error state. +func InjectErrorState(entry HistoryEntry, text string, debugArt *DebugArtifacts) { + if tpl, ok := MatchErrorTemplateByText(text); ok { + entry["suggested_questions"] = DefaultAIErrorSuggestedQuestions + card := tpl.ActionCard // copy + entry["action_card"] = &card + entry["debug_artifacts"] = debugArt + entry["is_error"] = true + } else if strings.Contains(text, ModelOutputErrorText) { + // Also check for format errors, which are hallucinations, not provider errors + entry["suggested_questions"] = DefaultAIErrorSuggestedQuestions + entry["action_card"] = &ActionCard{ + Title: "Formatting Error", + Description: "The model failed to format the response correctly.", + ButtonText: "Try Again", + ActionURL: "#retry", + } + entry["debug_artifacts"] = debugArt + entry["is_error"] = true + } +} diff --git a/internal/mcpagent/provider.go b/internal/mcpagent/provider.go index 472635773..c2c958c04 100644 --- a/internal/mcpagent/provider.go +++ b/internal/mcpagent/provider.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/livereview/internal/aiconnectors" "github.com/rs/zerolog/log" "github.com/tmc/langchaingo/llms" @@ -186,3 +185,4 @@ func (p *Provider) historyToMessages(history []HistoryEntry) []llms.MessageConte } return messages } + diff --git a/internal/mcpagent/provider_test.go b/internal/mcpagent/provider_test.go new file mode 100644 index 000000000..e4f64ee05 --- /dev/null +++ b/internal/mcpagent/provider_test.go @@ -0,0 +1,53 @@ +package mcpagent + +import ( + "testing" + + "github.com/livereview/internal/aiconnectors" +) + +func TestLLMErrorTemplates(t *testing.T) { + tests := []struct { + name string + category aiconnectors.LLMErrorCategory + want string + }{ + { + name: "Auth Error", + category: aiconnectors.ErrCategoryAuth, + want: "> **Action Required: AI Provider Issue**\n> \n> The AI Provider's API key is invalid or the model is missing. Please configure a valid provider in settings to continue.", + }, + { + name: "Overload Error", + category: aiconnectors.ErrCategoryOverload, + want: "> **AI Provider is Busy**\n> \n> The selected model is currently experiencing high traffic. If you continue to see this issue, please edit your configuration to change to a different model or provider.", + }, + { + name: "Timeout Error", + category: aiconnectors.ErrCategoryTimeout, + want: "> **Analysis Took Too Long**\n> \n> The AI took too long to generate your response and timed out. Try asking a narrower or more specific question.", + }, + { + name: "Unknown Error", + category: aiconnectors.ErrCategoryUnknown, + want: "> **Unexpected Error**\n> \n> An unexpected error occurred while communicating with the AI provider. The technical details have been attached below for troubleshooting.", + }, + { + name: "Unmapped Category", + category: aiconnectors.LLMErrorCategory("something_else"), + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := "" + if tpl, ok := LLMErrorTemplates[tt.category]; ok { + got = tpl.Message + } + if got != tt.want { + t.Errorf("LLMErrorTemplates[%v].Message = %q, want %q", tt.category, got, tt.want) + } + }) + } +} diff --git a/internal/mcpagent/types.go b/internal/mcpagent/types.go index e07130a39..77e2391f6 100644 --- a/internal/mcpagent/types.go +++ b/internal/mcpagent/types.go @@ -117,6 +117,7 @@ type DebugArtifacts struct { FullRequest string `json:"full_request"` // system_prompt + schema_context sent to LLM Interpretations []Interpretation `json:"interpretations"` Results []DebugResultEntry `json:"results"` + RawLLMError string `json:"raw_llm_error,omitempty"` } // DebugResultEntry is one interpretation's outcome for the debug view. @@ -134,3 +135,12 @@ type DebugResultEntry struct { RetryCount int `json:"retry_count,omitempty"` Retries []RetryInfo `json:"retries,omitempty"` } + +// ActionCard represents an actionable message for the UI to render as a card +// instead of plain text, like when a configuration change is required. +type ActionCard struct { + Title string `json:"title"` + Description string `json:"description"` + ButtonText string `json:"button_text"` + ActionURL string `json:"action_url"` +} diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 000000000..c31b1de56 --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,7 @@ +[[IgnoredVulns]] +id = "GHSA-vfj7-8cjw-p6xm" +reason = "braces DoS CVE is a dev dependency with no fix currently available. Safe to ignore." + +[[IgnoredVulns]] +id = "CVE-2026-93687" +reason = "braces DoS CVE is a dev dependency with no fix currently available. Safe to ignore." diff --git a/scripts/blast-radius-demo-export/static/components/SummarySlideshow/slideshowParser.js b/scripts/blast-radius-demo-export/static/components/SummarySlideshow/slideshowParser.js index 7127d4f39..7fbe8ebea 100644 --- a/scripts/blast-radius-demo-export/static/components/SummarySlideshow/slideshowParser.js +++ b/scripts/blast-radius-demo-export/static/components/SummarySlideshow/slideshowParser.js @@ -91,13 +91,19 @@ function countWords(text) { function extractPlainText(html) { const raw = html || ''; - if (typeof document === 'undefined') { - return raw.replace(/<[^>]+>/g, ' '); - } - - const container = document.createElement('div'); - container.innerHTML = raw; - return container.textContent || ''; + const withoutTags = raw.replace(/<[^>]+>/g, ' '); + + return withoutTags + .replace(/&[a-z]+;/gi, (match) => { + const entities = { + ' ': ' ', '&': '&', '<': '<', '>': '>', '"': '"', ''': "'" + }; + return entities[match.toLowerCase()] || match; + }) + .replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec)) + .replace(/&#x([0-9a-f]+);/gi, (match, hex) => String.fromCharCode(parseInt(hex, 16))) + .replace(/\s+/g, ' ') + .trim(); } function estimateReadTimeSeconds(text, title) { diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml index ed37dc04b..c678129b0 100644 --- a/scripts/pyproject.toml +++ b/scripts/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "playwright>=1.55.0", "pytest-playwright>=0.7.1", "pytest>=8.4.2", - "urllib3>=2.6.3", + "urllib3>=2.8.0", "werkzeug>=3.1.6", ] diff --git a/scripts/uv.lock b/scripts/uv.lock index 3cc3ed7c6..97be5e6aa 100644 --- a/scripts/uv.lock +++ b/scripts/uv.lock @@ -954,7 +954,7 @@ requires-dist = [ { name = "playwright", specifier = ">=1.55.0" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-playwright", specifier = ">=0.7.1" }, - { name = "urllib3", specifier = ">=2.6.3" }, + { name = "urllib3", specifier = ">=2.8.0" }, { name = "werkzeug", specifier = ">=3.1.6" }, ] @@ -1060,11 +1060,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]] diff --git a/tests/mcp/requirements.txt b/tests/mcp/requirements.txt index 160930ecd..bb827b12c 100644 --- a/tests/mcp/requirements.txt +++ b/tests/mcp/requirements.txt @@ -20,7 +20,7 @@ pycparser==3.0 pydantic==2.13.4 pydantic-settings==2.15.0 pydantic_core==2.46.4 -PyJWT==2.13.0 +PyJWT==2.15.0 python-dotenv==1.2.2 python-multipart==0.0.32 referencing==0.37.0 diff --git a/ui/package-lock.json b/ui/package-lock.json index 7475344b9..6c50fe5b2 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -44,7 +44,6 @@ "react-vega": "^8.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", - "shell-quote": "^1.9.0", "thinking-orbs": "^0.2.0", "vega": "^6.3.1", "vega-embed": "^7.1.0", @@ -2260,20 +2259,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@csstools/postcss-color-function": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.2.3.tgz", @@ -2480,20 +2465,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@csstools/postcss-logical-float-and-clear": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-1.0.1.tgz", @@ -2752,20 +2723,6 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@csstools/postcss-stepped-value-functions": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.1.1.tgz", @@ -7912,20 +7869,6 @@ "postcss": "^8.4" } }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/css-declaration-sorter": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", @@ -7978,20 +7921,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -11333,6 +11262,31 @@ "node": ">= 6" } }, + "node_modules/http-proxy-middleware": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, "node_modules/http-proxy/node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -15444,20 +15398,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-calc": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", @@ -15689,20 +15629,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-dir-pseudo-class": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-7.0.2.tgz", @@ -15723,20 +15649,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-discard-comments": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.6.tgz", @@ -15839,20 +15751,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-focus-within": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-7.0.2.tgz", @@ -15873,20 +15771,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-font-variant": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", @@ -16316,20 +16200,6 @@ "postcss": "^8.2.14" } }, - "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-nesting": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-11.3.0.tgz", @@ -16374,20 +16244,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-normalize-charset": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", @@ -16721,20 +16577,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-reduce-initial": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.6.tgz", @@ -16831,20 +16673,6 @@ "postcss": "^8.4" } }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-selector-parser": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", @@ -18626,6 +18454,7 @@ "version": "1.12.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -19452,20 +19281,6 @@ "node": ">=10" } }, - "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/tailwindcss/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -21308,31 +21123,6 @@ "node": ">= 6" } }, - "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", - "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, "node_modules/webpack-dev-server/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", diff --git a/ui/package.json b/ui/package.json index 638c957ca..ebe3914ef 100644 --- a/ui/package.json +++ b/ui/package.json @@ -118,7 +118,6 @@ "react-vega": "^8.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", - "shell-quote": "^1.9.0", "thinking-orbs": "^0.2.0", "vega": "^6.3.1", "vega-embed": "^7.1.0", @@ -174,6 +173,11 @@ "@opentelemetry/core": "^2.8.0", "dompurify": "^3.4.16", "undici": "^6.29.0", + "compression": "1.8.2", + "postcss-selector-parser": "7.1.6", + "proxy-addr": "2.0.8", + "shell-quote": "1.12.0", + "source-map-js": "1.2.2", "body-parser": "2.3.0" } } diff --git a/ui/src/api/chatConversations.ts b/ui/src/api/chatConversations.ts index 1cadb9f21..c40b0cd18 100644 --- a/ui/src/api/chatConversations.ts +++ b/ui/src/api/chatConversations.ts @@ -27,6 +27,13 @@ export interface ConversationMessage { files?: ChatFile[]; suggested_questions?: SuggestedQuestionCategory[]; debug_artifacts?: unknown; + is_error?: boolean; + action_card?: { + title: string; + description: string; + button_text: string; + action_url: string; + }; } export interface ConversationDetail { diff --git a/ui/src/api/chatbot.ts b/ui/src/api/chatbot.ts index f081e0712..4fbb4e419 100644 --- a/ui/src/api/chatbot.ts +++ b/ui/src/api/chatbot.ts @@ -140,8 +140,16 @@ export interface ChatResponse { files?: ChatFile[]; suggested_questions?: SuggestedQuestionCategory[]; debug_artifacts?: unknown; + is_error?: boolean; sessionId?: string; conversationId: number; + /** 'ai_auth' | 'ai_busy' — set only when the backend hit a provider error. */ + action_card?: { + title: string; + description: string; + button_text: string; + action_url: string; + }; } // The backend now owns conversation history: it loads prior turns by diff --git a/ui/src/pages/Chatbot/ChatActionCard.tsx b/ui/src/pages/Chatbot/ChatActionCard.tsx new file mode 100644 index 000000000..33de61d46 --- /dev/null +++ b/ui/src/pages/Chatbot/ChatActionCard.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ChatEntry } from './ChatConversation'; + +interface ChatActionCardProps { + msg: ChatEntry; + idx: number; + messages: ChatEntry[]; + handleSend: (text: string) => void; +} + +const ACTION_RETRY = '#retry'; + +export function ChatActionCard({ msg, idx, messages, handleSend }: ChatActionCardProps) { + const navigate = useNavigate(); + + if (!msg.actionCard) return null; + + return ( +
+
+
+

{msg.actionCard.title}

+

{msg.actionCard.description}

+
+ +
+ {msg.debugArtifacts?.raw_llm_error && ( +
+ + Debug logs + +
+ {msg.debugArtifacts.raw_llm_error} +
+
+ )} +
+ ); +} diff --git a/ui/src/pages/Chatbot/ChatConversation.tsx b/ui/src/pages/Chatbot/ChatConversation.tsx index 13e29e619..f406038b3 100644 --- a/ui/src/pages/Chatbot/ChatConversation.tsx +++ b/ui/src/pages/Chatbot/ChatConversation.tsx @@ -13,6 +13,8 @@ import { CONVERSATIONS_QUERY_KEY } from './ConversationSidebar'; import { isDailyTrendChart, buildTrendSpec, formatAxisDate, Granularity } from './rebucketChart'; import { buildJsonExport, downloadJsonExport } from './jsonExport'; import ProductionUrlWarning from '../../components/ProductionUrlWarning'; +import { ChatActionCard } from './ChatActionCard'; +import { ChatErrorBlockquote } from './ChatErrorHandling'; const GRANULARITY_OPTIONS: { value: Granularity; label: string }[] = [ { value: 'day', label: 'Day' }, @@ -68,9 +70,7 @@ const ContextDetails: React.FC<{ context: ChartContext }> = ({ context }) => ( // Unified logo wrapper to ensure the 16px visual right gap and 8px visual left gap // stay mathematically synchronized across the header, chat messages, and loading states. const LiviLogo: React.FC<{ className?: string }> = ({ className = '' }) => ( -
- Bot -
+ Bot ); // Debug artifacts (SQL, CSV, Vega spec, schema context, system prompt, raw @@ -107,9 +107,10 @@ interface DebugArtifacts { repaired_sql?: string; }>; }>; + raw_llm_error?: string; } -interface ChatEntry { +export interface ChatEntry { id: string; role: 'user' | 'assistant'; text: string; @@ -117,6 +118,13 @@ interface ChatEntry { files?: ChatFile[]; suggestedQuestions?: SuggestedQuestionCategory[]; debugArtifacts?: DebugArtifacts | null; + isError?: boolean; + actionCard?: { + title: string; + description: string; + button_text: string; + action_url: string; + }; } function formatRowCount(rows?: number): string { @@ -169,7 +177,7 @@ function extractTrailingDataDetails(text: string): { body: string; details: { la return { body: lines.slice(0, end).join('\n'), details }; } -function formatText(rawText: string): React.ReactNode[] { +function formatText(rawText: string, isErrorMsg: boolean = false): React.ReactNode[] { const { body: text, details } = extractTrailingDataDetails(rawText); const parts: React.ReactNode[] = []; // Ensure buttons are always inline with the preceding text by stripping newlines before them @@ -217,22 +225,13 @@ function formatText(rawText: string): React.ReactNode[] { } i = currI - 1; - const isError = blockquoteLines.length > 0 && blockquoteLines[0].includes('Action Required:'); - - if (isError) { + if (isErrorMsg) { parts.push( -
-
- {formatLine(blockquoteLines[0])} -
-
- {blockquoteLines.slice(1).map((bLine, bIdx) => ( -
*:first-child]:mt-0'}> - {formatLine(bLine)} -
- ))} -
-
+ ); } else { parts.push( @@ -891,6 +890,8 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } files: m.files && m.files.length > 0 ? m.files : undefined, suggestedQuestions: m.suggested_questions, debugArtifacts: m.debug_artifacts as DebugArtifacts | undefined, + isError: m.is_error, + actionCard: m.action_card, })), ); }, [conversationDetail]); @@ -911,10 +912,13 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } pendingConversationIdRef.current = undefined; }, [conversationId]); - const handleSend = async () => { - const text = input.trim(); + const handleSend = async (overrideText?: string) => { + const isString = typeof overrideText === 'string'; + const text = (isString ? overrideText : input).trim(); if (!text || isLoading) return; - setInput(''); + if (!isString) { + setInput(''); + } const userEntry: ChatEntry = { id: generateId(), role: 'user', text }; setMessages((prev) => [...prev, userEntry]); @@ -942,6 +946,8 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } files: result.files && result.files.length > 0 ? result.files : undefined, suggestedQuestions: result.suggested_questions, debugArtifacts: result.debug_artifacts as DebugArtifacts | undefined, + isError: result.is_error, + actionCard: result.action_card, }; setMessages((prev) => [...prev, assistantEntry]); queryClient.invalidateQueries({ queryKey: CONVERSATIONS_QUERY_KEY }); @@ -971,6 +977,13 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } files: entry.files, suggested_questions: entry.suggestedQuestions, debug_artifacts: entry.debugArtifacts, + // Must mirror what GetConversation returns per message. The + // remount below re-hydrates from this cache, so anything dropped + // here disappears from the reply the user just received — an + // action card silently vanishes on a brand-new conversation and + // only comes back on refresh. + action_card: entry.actionCard, + is_error: entry.isError, })), }); navigate(`${basePath}/${activeConversationId}`, { replace: true }); @@ -1032,7 +1045,14 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } { id: generateId(), role: 'assistant', - text: 'I am sorry, but I ran into an issue while thinking about that. Please try asking again.', + text: '> **Analysis Took Too Long**\n> \n> We lost connection to the server or the request timed out. Please try asking again.', + isError: true, + actionCard: { + title: 'Timeout Error', + description: 'The request took too long to complete.', + button_text: 'Try Again', + action_url: '#retry' + } }, ]); } @@ -1046,7 +1066,14 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } { id: generateId(), role: 'assistant', - text: 'I am sorry, but I ran into an issue while thinking about that. Please try asking again.', + text: '> **Network Error**\n> \n> We lost connection to the server or the request failed. Please check your network and try again.', + isError: true, + actionCard: { + title: 'Network Error', + description: 'The connection failed.', + button_text: 'Try Again', + action_url: '#retry' + } }, ]); setIsLoading(false); @@ -1082,8 +1109,11 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } const debugModalMsg = debugModalMsgId ? messages.find((m) => m.id === debugModalMsgId) : undefined; return ( -
-
+
+ {/* Header: pl-4 pr-6 instead of px-4 so max-w-4xl mx-auto centers identically + to the messages section which has scrollbar-gutter:stable (8px) on the right. + Math: header right=24px, messages right=16px(px-4)+8px(gutter)=24px → same. */} +
@@ -1142,7 +1172,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface }
-
+
{isSuperAdmin && !dismissedProdUrlWarning && ( setDismissedProdUrlWarning(true)} /> @@ -1202,7 +1232,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface }
) : (
- {messages.map((msg) => + {messages.map((msg, idx) => msg.role === 'user' ? (
@@ -1211,7 +1241,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface }
) : (
- +
{msg.charts && msg.charts.length > 0 && (
@@ -1400,7 +1430,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } )} {(chart.query || chart.time_range || chart.granularity || chart.context) && (
- + Data details
@@ -1451,7 +1481,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } )} {(file.query || file.time_range || file.granularity || file.context) && (
- + Data details
@@ -1472,34 +1502,14 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } ))}
)} - {msg.text && (() => { - const isAiError = msg.text.includes('Action Required: AI Provider Issue'); - const displayText = isAiError && msg.text.includes('Please configure a valid provider to continue:') - ? msg.text.split('Please configure a valid provider to continue:')[0].trim() - : msg.text; - - return ( - <> -
0) || (msg.files && msg.files.length > 0) ? 'mt-6' : ''} text-base leading-snug whitespace-pre-wrap break-words text-slate-200 [&>*:first-child]:mt-0`}> - {formatText(displayText)} -
- {isAiError && ( -
-
-

Configuration Required

-

Please edit the current AI provider model to continue.

-
- -
- )} - - ); - })()} + {msg.text && ( +
0) || (msg.files && msg.files.length > 0) ? 'mt-6' : ''} text-base leading-snug whitespace-pre-wrap break-words text-slate-200 [&>*:first-child]:mt-0`}> + {formatText(msg.text, msg.isError)} +
+ )} + {msg.actionCard && ( + + )} {msg.suggestedQuestions && msg.suggestedQuestions.length > 0 && (
{msg.suggestedQuestions.map((cat: SuggestedQuestionCategory, catIdx: number) => ( @@ -1536,7 +1546,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface } {isLoading && (
- +
@@ -1546,7 +1556,7 @@ export const ChatConversation: React.FC<{ surface: ChatSurface }> = ({ surface }
-
+
= ({ surface } className="w-full bg-slate-700 text-slate-100 placeholder-slate-400 rounded-full pl-4 pr-14 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 border border-slate-600 disabled:opacity-50" />