diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4b564af0e20..897e896f442 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4321,6 +4321,53 @@ Keep-alive heartbeats (every 30 s by default): --- +## QA Recording — Automated Video Capture and Retention + +### QA Recording Data Flow + +``` +User asks to test → QA intent detection (regex/keyword) → CU session created with qaMode +→ reportToSessionId set if conversationId available (chat context) or sourceSessionId (escalation) +→ macOS ScreenRecorder starts → CU action loop executes +→ Session terminates → ScreenRecorder stops → .mp4 saved to ~/Library/Application Support/vellum-assistant/recordings/ +→ cu_session_finalized sent to daemon with recording metadata +→ Daemon handler creates file-backed attachment (always, for cleanup tracking) +→ If reportToSessionId present: also injects assistant message + attachment into source chat +→ Client loads video from GET /v1/attachments/:id/content (with Range support) +→ Video playable inline + draggable to Finder +→ Retention cleanup removes expired recordings after configurable period (qaRecording.defaultRetentionDays) +``` + +### File-Backed Attachment Storage + +The attachments table supports two storage kinds: + +| `storageKind` | Data location | Use case | +|---------------|---------------|----------| +| `inline_base64` | `dataBase64` column in SQLite | Small attachments (images, documents, up to 20 MB) | +| `file` | On-disk file referenced by `filePath` column | Large files (QA recordings, videos) | + +File-backed attachments store only metadata in SQLite (filename, MIME type, size, SHA-256 hash, expiry timestamp). Binary content is served via `GET /v1/attachments/:id/content` with HTTP Range header support for streaming video playback. + +### Retention Cleanup + +A periodic cleanup worker (`recording-cleanup.ts`) runs on a configurable interval (default: every 6 hours, set via `qaRecording.cleanupIntervalMs`). It also runs one pass on daemon startup to catch recordings that expired while the daemon was offline. + +The cleanup pass: +1. Queries `getExpiredFileAttachments()` for file-backed attachments where `expiresAt < now` +2. Deletes the underlying file from disk via `fs.unlinkSync` +3. Removes the DB row via `deleteFileBackedAttachment(id)` +4. Logs a summary of cleaned-up recordings and freed disk space + +| Key files | Purpose | +|-----------|---------| +| `assistant/src/daemon/recording-cleanup.ts` | Cleanup worker (start/stop/runPass) | +| `assistant/src/memory/attachments-store.ts` | `createFileBackedAttachment`, `getExpiredFileAttachments`, `deleteFileBackedAttachment` | +| `assistant/src/config/schema.ts` | `QaRecordingConfigSchema` (retention days, cleanup interval) | +| `assistant/src/daemon/lifecycle.ts` | Wires cleanup worker start/stop into daemon init/shutdown | + +--- + ## Storage Summary | What | Where | Format | ORM/Driver | Retention | @@ -4338,7 +4385,8 @@ Keep-alive heartbeats (every 30 s by default): | Entity graph (entities/relations/item links) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped by unique relation edge | | Embeddings | `~/.vellum/workspace/data/db/assistant.db` | JSON float arrays | Drizzle ORM | Permanent | | Async job queue | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Completed jobs persist | -| Attachments | `~/.vellum/workspace/data/db/assistant.db` | Base64 in SQLite | Drizzle ORM | Permanent | +| Attachments (inline) | `~/.vellum/workspace/data/db/assistant.db` | Base64 in SQLite | Drizzle ORM | Permanent | +| Attachments (file-backed) | `~/Library/Application Support/vellum-assistant/recordings/` + metadata in SQLite | Binary on disk, metadata in SQLite | Drizzle ORM + fs | Configurable (`qaRecording.defaultRetentionDays`, default 7 days) | | Sandbox filesystem | `~/.vellum/workspace` | Real filesystem tree | Node FS APIs | Persistent across sessions | | Tool permission rules | `~/.vellum/protected/trust.json` | JSON | File I/O | Permanent | | Web users & assistants | PostgreSQL | Relational | Drizzle ORM (pg) | Permanent | diff --git a/assistant/package-lock.json b/assistant/package-lock.json new file mode 100644 index 00000000000..361443a7c25 --- /dev/null +++ b/assistant/package-lock.json @@ -0,0 +1,10997 @@ +{ + "name": "@vellumai/assistant", + "version": "0.3.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@vellumai/assistant", + "version": "0.3.4", + "dependencies": { + "@anthropic-ai/claude-agent-sdk": "^0.2.42", + "@anthropic-ai/sdk": "^0.39.0", + "@google/genai": "^1.40.0", + "@huggingface/transformers": "^3.8.1", + "@qdrant/js-client-rest": "^1.16.2", + "@sentry/node": "^10.38.0", + "agentmail": "^0.1.0", + "archiver": "^7.0.1", + "commander": "^13.1.0", + "croner": "^10.0.1", + "dotenv": "^17.3.1", + "drizzle-orm": "^0.38.4", + "ink": "^6.7.0", + "jszip": "^3.10.1", + "minimatch": "^10.1.2", + "openai": "^6.18.0", + "pino": "^9.6.0", + "pino-pretty": "^13.1.3", + "playwright": "^1.58.2", + "postgres": "^3.4.8", + "react": "^19.2.4", + "rrule": "^2.8.1", + "tldts": "^7.0.23", + "tree-sitter-bash": "0.25.1", + "uuid": "^11.1.0", + "web-tree-sitter": "0.26.5", + "zod": "^4.3.6" + }, + "bin": { + "vellum": "src/index.ts" + }, + "devDependencies": { + "@pydantic/logfire-node": "^0.13.0", + "@types/archiver": "^7.0.0", + "@types/bun": "^1.2.4", + "@types/node": "^25.2.2", + "@types/react": "^19.2.14", + "@types/uuid": "^10.0.0", + "drizzle-kit": "^0.30.4", + "eslint": "^10.0.0", + "fast-check": "^4.5.3", + "knip": "^5.83.1", + "quicktype-core": "^23.2.6", + "typescript": "^5.7.3", + "typescript-eslint": "^8.54.0", + "typescript-json-schema": "^0.67.1" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@anthropic-ai/claude-agent-sdk": { + "version": "0.2.50", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.50.tgz", + "integrity": "sha512-zVQzJbicfTmvS6uarFQYYVYiYedKE0FgXmhiGC1oSLm6OkIbuuKM7XV4fXEFxPZHcWQc7ZYv6HA2/P5HOE7b2Q==", + "license": "SEE LICENSE IN README.md", + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "^0.34.2", + "@img/sharp-darwin-x64": "^0.34.2", + "@img/sharp-linux-arm": "^0.34.2", + "@img/sharp-linux-arm64": "^0.34.2", + "@img/sharp-linux-x64": "^0.34.2", + "@img/sharp-linuxmusl-arm64": "^0.34.2", + "@img/sharp-linuxmusl-x64": "^0.34.2", + "@img/sharp-win32-arm64": "^0.34.2", + "@img/sharp-win32-x64": "^0.34.2" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", + "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@apm-js-collab/code-transformer": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz", + "integrity": "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==", + "license": "Apache-2.0" + }, + "node_modules/@apm-js-collab/tracing-hooks": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.1.tgz", + "integrity": "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==", + "license": "Apache-2.0", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.8.0", + "debug": "^4.4.1", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.2", + "debug": "^4.3.1", + "minimatch": "^10.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@glideapps/ts-necessities": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz", + "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@google/genai": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.42.0.tgz", + "integrity": "sha512-+3nlMTcrQufbQ8IumGkOphxD5Pd5kKyJOzLcnY0/1IuE8upJk5aLmoexZ2BJhBp1zAjRJMEB4a2CJwKI9e2EYw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@huggingface/jinja": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.5.tgz", + "integrity": "sha512-xRlzazC+QZwr6z4ixEqYHo9fgwhTZ3xNSdljlKfUFGZSdlvt166DljRELFUfFytlYOYvo3vTisA/AFOuOAzFQQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@huggingface/transformers": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.8.1.tgz", + "integrity": "sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA==", + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.5.3", + "onnxruntime-node": "1.21.0", + "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4", + "sharp": "^0.34.1" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "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==", + "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==", + "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/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.210.0.tgz", + "integrity": "sha512-CMtLxp+lYDriveZejpBND/2TmadrrhUfChyxzmkFtHaMDdSKfP59MAYyA0ICBvEBdm3iXwLcaj/8Ic/pnGw9Yg==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/auto-instrumentations-node": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.68.0.tgz", + "integrity": "sha512-WgLKBVG4hkaJYSfc/FbZsTr34gHVvMBd64Lw/u1bg3FLRGsd3Ys91672YRrj+7XPQXzwTew39sJRKMj03utRng==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-amqplib": "^0.57.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.62.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.65.0", + "@opentelemetry/instrumentation-bunyan": "^0.55.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.55.0", + "@opentelemetry/instrumentation-connect": "^0.53.0", + "@opentelemetry/instrumentation-cucumber": "^0.25.0", + "@opentelemetry/instrumentation-dataloader": "^0.27.0", + "@opentelemetry/instrumentation-dns": "^0.53.0", + "@opentelemetry/instrumentation-express": "^0.58.0", + "@opentelemetry/instrumentation-fastify": "^0.54.0", + "@opentelemetry/instrumentation-fs": "^0.29.0", + "@opentelemetry/instrumentation-generic-pool": "^0.53.0", + "@opentelemetry/instrumentation-graphql": "^0.57.0", + "@opentelemetry/instrumentation-grpc": "^0.210.0", + "@opentelemetry/instrumentation-hapi": "^0.56.0", + "@opentelemetry/instrumentation-http": "^0.210.0", + "@opentelemetry/instrumentation-ioredis": "^0.58.0", + "@opentelemetry/instrumentation-kafkajs": "^0.19.0", + "@opentelemetry/instrumentation-knex": "^0.54.0", + "@opentelemetry/instrumentation-koa": "^0.58.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.54.0", + "@opentelemetry/instrumentation-memcached": "^0.53.0", + "@opentelemetry/instrumentation-mongodb": "^0.63.0", + "@opentelemetry/instrumentation-mongoose": "^0.56.0", + "@opentelemetry/instrumentation-mysql": "^0.56.0", + "@opentelemetry/instrumentation-mysql2": "^0.56.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.56.0", + "@opentelemetry/instrumentation-net": "^0.54.0", + "@opentelemetry/instrumentation-openai": "^0.8.0", + "@opentelemetry/instrumentation-oracledb": "^0.35.0", + "@opentelemetry/instrumentation-pg": "^0.62.0", + "@opentelemetry/instrumentation-pino": "^0.56.0", + "@opentelemetry/instrumentation-redis": "^0.58.0", + "@opentelemetry/instrumentation-restify": "^0.55.0", + "@opentelemetry/instrumentation-router": "^0.54.0", + "@opentelemetry/instrumentation-runtime-node": "^0.23.0", + "@opentelemetry/instrumentation-socket.io": "^0.56.0", + "@opentelemetry/instrumentation-tedious": "^0.29.0", + "@opentelemetry/instrumentation-undici": "^0.20.0", + "@opentelemetry/instrumentation-winston": "^0.54.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.33.0", + "@opentelemetry/resource-detector-aws": "^2.10.0", + "@opentelemetry/resource-detector-azure": "^0.18.0", + "@opentelemetry/resource-detector-container": "^0.8.1", + "@opentelemetry/resource-detector-gcp": "^0.45.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-node": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^2.0.0" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.210.0.tgz", + "integrity": "sha512-tM0ROS/hZM72kB55cSjDcghVcUXBJdGkGzpkhD7M1B/gpcvZPSGfjFgKN3dgmxNgF76NxtbUwv3ik0wS+Kz52g==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/configuration/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz", + "integrity": "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", + "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.210.0.tgz", + "integrity": "sha512-+BolenqOO6ow65go7uWRYPvvs/BBIWp1mtRn93VvGduqvMVH/IY8nXrt80a4L9hZ7lHi2Tq2/NcC3H2QzcWKag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/sdk-logs": "0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.210.0.tgz", + "integrity": "sha512-Q8/SEQtgrErbVVRg9M9iaG8m5wdPNdU0UOF7U43sAhwfmPG92ZOk/aenKhg0DXSNJHhkCDNCgS1kSoErAB3z0A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.210.0", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/sdk-logs": "0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.210.0.tgz", + "integrity": "sha512-Y/yPc+gDhsWB7AsNzQWxblw4ULbvhCycMaQ2aAn+HSAVbgbMiZa0SbclPVHSnpnNzKSLVavFjweAr0pQA1KKLg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.210.0", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-logs": "0.210.0", + "@opentelemetry/sdk-trace-base": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.210.0.tgz", + "integrity": "sha512-pWZ/Tjrqev9rdkqe8F6A9FGddLZrjl6iRAU5LBvvRL6I3PSgG8z1xM0cESAy1jzAF4wGohnAh8rB7hHzpUOYEA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.210.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-metrics": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.4.0.tgz", + "integrity": "sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.210.0.tgz", + "integrity": "sha512-JpLThG8Hh8A/Jzdzw9i4Ftu+EzvLaX/LouN+mOOHmadL0iror0Qsi3QWzucXeiUsDDsiYgjfKyi09e6sltytgA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-metrics": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.4.0.tgz", + "integrity": "sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.210.0.tgz", + "integrity": "sha512-CFa7SOinYOVWIWJuQL7XFeyedzmFGIpHpSMNFE8Xefb6iGB4m+MukQecdssvPcJKYlfF5FpovEOLXwafAzsXWQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.210.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-metrics": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.4.0.tgz", + "integrity": "sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.210.0.tgz", + "integrity": "sha512-8i+7d70Hho6pcheTtbqIuS+bo+AIX/oNUTMwIEZoehUE4ZdbGmeVaE+hJS2LAErFeFaU71w164lAgYyMUEQ8zw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-metrics": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.4.0.tgz", + "integrity": "sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.210.0.tgz", + "integrity": "sha512-1GPLOyxIfUX24WM8Oea+vx9d9TlewposUnsQXTjusxVMQ/dWvt5JIDJyTsfNDS412XRUOORgF97PwsfDY5QKGA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.210.0.tgz", + "integrity": "sha512-9JkyaCl70anEtuKZdoCQmjDuz1/paEixY/DWfsvHt7PGKq3t8/nQ/6/xwxHjG+SkPAUbo1Iq4h7STe7Pk2bc5A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.210.0.tgz", + "integrity": "sha512-qVUY7Hsm/t5buGOtPcTV1Ch4W9kj2wGaQaAF5FO4XR8TMKl2GM45tUCnr0/1dF3wo4RG9khMxrddeQWdRL4fIg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.4.0.tgz", + "integrity": "sha512-qpiXY0TUEFjBBp9b1na9LfuVQw6W8LH+te7uv+CC+0Up78ZDtZZwOjK2M7CL7Nspnw+yS4JdgEA7oxsBu0Ctsg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.210.0.tgz", + "integrity": "sha512-sLMhyHmW9katVaLUOKpfCnxSGhZq2t1ReWgwsu2cSgxmDVMB690H9TanuexanpFI94PJaokrqbp8u9KYZDUT5g==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.210.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.57.0.tgz", + "integrity": "sha512-hgHnbcopDXju7164mwZu7+6mLT/+O+6MsyedekrXL+HQAYenMqeG7cmUOE0vI6s/9nW08EGHXpD+Q9GhLU1smA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-aws-lambda": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.62.0.tgz", + "integrity": "sha512-EbDyOwdN4ndn0JJq7qacZLSCxrm72lj/2j98/MjakCuTG15nBJ/R4OkdzOmmoPbtvnrgGzzBZBiaDYoviJEAFA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/aws-lambda": "^8.10.155" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-aws-sdk": { + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.65.0.tgz", + "integrity": "sha512-nrKIhTlBxFr/wvjk2vZ6eCcyc41eOQVTMR+ux4FM0gNvK+DgggE+RnkycGATP5lJKjltn+wrYNP2E2tmxCtF1A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.34.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-bunyan": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.55.0.tgz", + "integrity": "sha512-/iBimXTUbxsEHpLafOLiYhinS8NQo2pRhkJAeviepfSegJkBnR9ACu5YoiJN/CsKM6HpW8UTpecZXHfu+rM0CQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "^0.210.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@types/bunyan": "1.8.11" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-cassandra-driver": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.55.0.tgz", + "integrity": "sha512-o7ud8Fcg6HFKooWKSWk8ouMVGy3UBv6jg5PVQp/teng/tw7tbLNlZNGW7W6IzUcVfpToBfkh78iQPAKrzryLfg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.37.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.53.0.tgz", + "integrity": "sha512-SoFqipWLUEYVIxvz0VYX9uWLJhatJG4cqXpRe1iophLofuEtqFUn8YaEezjz2eJK74eTUQ0f0dJVOq7yMXsJGQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-cucumber": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.25.0.tgz", + "integrity": "sha512-0Rmrt2DJjinfeuThg1E6Rfr7vGOnrrQxezh3QV1YtVpp8pY+365CsBLjTJmQ2J6zsSWbbZJ0l9Fhtjw13a78Wg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.27.0.tgz", + "integrity": "sha512-8e7n8edfTN28nJDpR/H59iW3RbW1fvpt0xatGTfSbL8JS4FLizfjPxO7JLbyWh9D3DSXxrTnvOvXpt6V5pnxJg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dns": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.53.0.tgz", + "integrity": "sha512-/m4KxS7rWkQpTLJW77cyt0pNzdcgjm2at4XD0nLGhHJz2G3x8GXQ6QOLRc3kPYt1WHJvzQ2UgzjKDz7f83PUXQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.58.0.tgz", + "integrity": "sha512-UuGst6/1XPcswrIm5vmhuUwK/9qx9+fmNB+4xNk3lfpgQlnQxahy20xmlo3I+LIyA5ZA3CR2CDXslxAMqwminA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fastify": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.54.0.tgz", + "integrity": "sha512-1sIJmA7wuvtNSrFbQek9rl2SNXvQ2JNuitDixL0kWiqL/UkJYkeSSegxmuEg52AAcO5Aa2OtJc0L2Syz/XROYw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.29.0.tgz", + "integrity": "sha512-JXPygU1RbrHNc5kD+626v3baV5KamB4RD4I9m9nUTd/HyfLZQSA3Z2z3VOebB3ChJhRDERmQjLiWvwJMHecKPg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.53.0.tgz", + "integrity": "sha512-h49axGXGlvWzyQ4exPyd0qG9EUa+JP+hYklFg6V+Gm4ZC2Zam1QeJno/TQ8+qrLvsVvaFnBjTdS53hALpR3h3Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.57.0.tgz", + "integrity": "sha512-wjtSavcp9MsGcnA1hj8ArgsL3EkHIiTLGMwqVohs5pSnMGeao0t2mgAuMiv78KdoR3kO3DUjks8xPO5Q6uJekg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-grpc": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.210.0.tgz", + "integrity": "sha512-gwXtFydErdqM6Vq/DMNst1Vb6aRPdZHIA155rgD06QGeqyg+0RQxtW3SCmCzGMwrlMTrqPBIfG/v757Zi4skLA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "0.210.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.56.0.tgz", + "integrity": "sha512-HgLxgO0G8V9y/6yW2pS3Fv5M3hz9WtWUAdbuszQDZ8vXDQSd1sI9FYHLdZW+td/8xCLApm8Li4QIeCkRSpHVTg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.210.0.tgz", + "integrity": "sha512-dICO+0D0VBnrDOmDXOvpmaP0gvai6hNhJ5y6+HFutV0UoXc7pMgJlJY3O7AzT725cW/jP38ylmfHhQa7M0Nhww==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/instrumentation": "0.210.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.58.0.tgz", + "integrity": "sha512-2tEJFeoM465A0FwPB0+gNvdM/xPBRIqNtC4mW+mBKy+ZKF9CWa7rEqv87OODGrigkEDpkH8Bs1FKZYbuHKCQNQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.19.0.tgz", + "integrity": "sha512-PMJePP4PVv+NSvWFuKADEVemsbNK8tnloHnrHOiRXMmBnyqcyOTmJyPy6eeJ0au90QyiGB2rzD8smmu2Y0CC7A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.54.0.tgz", + "integrity": "sha512-XYXKVUH+0/Ur29jMPnyxZj32MrZkWSXHhCteTkt/HzynKnvIASmaAJ6moMOgBSRoLuDJFqPew68AreRylIzhhg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.58.0.tgz", + "integrity": "sha512-602W6hEFi3j2QrQQBKWuBUSlHyrwSCc1IXpmItC991i9+xJOsS4n4mEktEk/7N6pavBX35J9OVkhPDXjbFk/1A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.54.0.tgz", + "integrity": "sha512-LPji0Qwpye5e1TNAUkHt7oij2Lrtpn2DRTUr4CU69VzJA13aoa2uzP3NutnFoLDUjmuS6vi/lv08A2wo9CfyTA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-memcached": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.53.0.tgz", + "integrity": "sha512-ni6B1n5wdY3XsbfL74Ix5yKQsXRerrgqmhK595ICgkxlU6JDwxoaCmoGmLCKDS/Nr0p3XhIfPVvjOPCfK73nUw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/memcached": "^2.2.6" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.63.0.tgz", + "integrity": "sha512-EvJb3aLiq1QedAZO4vqXTG0VJmKUpGU37r11thLPuL5HNa08sUS9DbF69RB8YoXVby2pXkFPMnbG0Pky0JMlKA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.56.0.tgz", + "integrity": "sha512-1xBjUpDSJFZS4qYc4XXef0pzV38iHyKymY4sKQ3xPv7dGdka4We1PsuEg6Z8K21f1d2Yg5eU0OXXRSPVmowKfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.56.0.tgz", + "integrity": "sha512-osdGMB3vc4bm1Kos04zfVmYAKoKVbKiF/Ti5/R0upDEOsCnrnUm9xvLeaKKbbE2WgJoaFz3VS8c99wx31efytQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.56.0.tgz", + "integrity": "sha512-rW0hIpoaCFf55j0F1oqw6+Xv9IQeqJGtw9MudT3LCuhqld9S3DF0UEj8o3CZuPhcYqD+HAivZQdrsO5XMWyFqw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-nestjs-core": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.56.0.tgz", + "integrity": "sha512-2wKd6+/nKyZVTkElTHRZAAEQ7moGqGmTIXlZvfAeV/dNA+6zbbl85JBcyeUFIYt+I42Naq5RgKtUY8fK6/GE1g==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-net": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.54.0.tgz", + "integrity": "sha512-Vpfw1AXCGbIdL+xrvXWIx/l1dG3H7kixvFLuOY8QWFsw5+nThAUKwVCVau4VIMzWnY9TC1Oa86NIEc0ILga4CQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-openai": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-openai/-/instrumentation-openai-0.8.0.tgz", + "integrity": "sha512-iX/AZLXrbRfwhOv7Cn5vNHR+o3tvtjAi44r2tS0eL1+lW75IvkN4SK6NDJjqWBEv2sIM1TsqydOMfUf1fV1sxw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "^0.210.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-oracledb": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-oracledb/-/instrumentation-oracledb-0.35.0.tgz", + "integrity": "sha512-V9DG842WFbcjtb9EpSRcA49vySKAzM7csVk490wOrxsjZ0QCliUQ8GH06cnYiSQi/OOYS2NMPuRKQNhrDWB8Jw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@types/oracledb": "6.5.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.62.0.tgz", + "integrity": "sha512-/ZSMRCyFRMjQVx7Wf+BIAOMEdN/XWBbAGTNLKfQgGYs1GlmdiIFkUy8Z8XGkToMpKrgZju0drlTQpqt4Ul7R6w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pino": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.56.0.tgz", + "integrity": "sha512-S2YMh+xfLanyhhGSzZwFxO8iUFJoSdBO/qlndSbkrmlydFJrOrA3nyZQclM0E1i3IN+uXjMJkGRN7B5R7am+yg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "^0.210.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.58.0.tgz", + "integrity": "sha512-tOGxw+6HZ5LDpMP05zYKtTw5HPqf3PXYHaOuN+pkv6uIgrZ+gTT75ELkd49eXBpjg3t36p8bYpsLgYcpIPqWqA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-restify": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.55.0.tgz", + "integrity": "sha512-vpAHMoiLGdKz44zFzop283JLksuuO9EM7ap03cj0UgbxcaEjjLGkIv2qAEcICtYi/1LBRGHk1fXlmUg3Mu36dQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-router": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.54.0.tgz", + "integrity": "sha512-N+PATM9akOUnfQTYnc0eDb6uRxpCkZMLFGxWgDIc0SclTKYqhu8WQjHPWK82YnUQ3ghgt+3BckPZihiOctRhdA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-runtime-node": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.23.0.tgz", + "integrity": "sha512-CWq1xxuVUkOqOAzTcEiNvgT/rxKpoegC4z92eNqeYS5e71OTU8DeFZ9bNrtbb1YtbYCeY4ROBPyhMWJl27br3w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-socket.io": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.56.0.tgz", + "integrity": "sha512-YueOOdNsMI9vUv+T8VMDv5TLEoBLF/UFfgr/InZ+H9+WRBhG9iGaRFJ8cvjx1EOz/wP5nFdcBgffMyphjhWYQA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.29.0.tgz", + "integrity": "sha512-Jtnayb074lk7DQL25pOOpjvg4zjJMFjFWOLlKzTF5i1KxMR4+GlR/DSYgwDRfc0a4sfPXzdb/yYw7jRSX/LdFg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.20.0.tgz", + "integrity": "sha512-VGBQ89Bza1pKtV12Lxgv3uMrJ1vNcf1cDV6LAXp2wa6hnl6+IN6lbEmPn6WNWpguZTZaFEvugyZgN8FJuTjLEA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/instrumentation-winston": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.54.0.tgz", + "integrity": "sha512-RH8HVPXrSYgozn+D3SANXcDxt3Xcd8If85JWmGRTns45Hu8YfXA3DEVonA8YfVg4zvvEJbGg+RFbCddAX/6LaA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "^0.210.0", + "@opentelemetry/instrumentation": "^0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.210.0.tgz", + "integrity": "sha512-uk78DcZoBNHIm26h0oXc8Pizh4KDJ/y04N5k/UaI9J7xR7mL8QcMcYPQG9xxN7m8qotXOMDRW6qTAyptav4+3w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-transformer": "0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.210.0.tgz", + "integrity": "sha512-fEJs8UhkFMrdXMOCLXyKd2uc6N209tIi8IBNqSTi83ri+MlMFrBKnOtklmv9/zzxovoN5zD1waRt6XBFGPfmIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/otlp-exporter-base": "0.210.0", + "@opentelemetry/otlp-transformer": "0.210.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.210.0.tgz", + "integrity": "sha512-nkHBJVSJGOwkRZl+BFIr7gikA93/U8XkL2EWaiDbj3DVjmTEZQpegIKk0lT8oqQYfP8FC6zWNjuTfkaBVqa0ZQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.210.0", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-logs": "0.210.0", + "@opentelemetry/sdk-metrics": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0", + "protobufjs": "8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.4.0.tgz", + "integrity": "sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.4.0.tgz", + "integrity": "sha512-6VPsFiMUkJBre/86F0d+PZMaUCcuLA9DtZuC46KH8EeVEKZPEM2WlX35M/qmde8UpzoQL9qzdz54YjUYABt8Uw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.4.0.tgz", + "integrity": "sha512-t6muBL/3AMD++1EMF658C/KIpj3gfmTmftX3mEQql4KIxNGFvacCmmTtrQt9IZAJmQRfjQRCkv+vsGbQugeJIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", + "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.33.2.tgz", + "integrity": "sha512-EaS54zwYmOg9Ttc79juaktpCBYqyh2IquXl534sLls+c1/pc8LZfWPMqytFt+iBvSPQ6ajraUnvi6cun4AhSjQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resource-detector-aws": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-2.12.0.tgz", + "integrity": "sha512-VelueKblsnQEiBVqEYcvM9VEb+B8zN6nftltdO9HAD7qi/OlicP4z/UGJ9EeW2m++WabdMoj0G3QVL8YV0P9tw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resource-detector-azure": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.18.0.tgz", + "integrity": "sha512-7byUo/Gimruh23vA8H2q4/cWxGe7YOTBjIKpoPjt/9yGQ2PUF3s6k2SQrMxaonTwqVmQgW29DU3SHLfd0kHjhg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.37.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resource-detector-container": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.8.3.tgz", + "integrity": "sha512-5J0JP2cy655rBKM9Doz26ffO3rG+Xqm7OXeNXkckzmc3JmL6Bj3dPBKugPYsfemhEIqtf7INH9UmPQqTMuWoHg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resource-detector-gcp": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.45.0.tgz", + "integrity": "sha512-u1AshqWqiiSblTix+8zzR9hcUWiHMNW/4WUnOecZ3FgNMkJJ57rkZvQKxaK5mfetP0s1OfAbiq09krQ1riO/Rw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "gcp-metadata": "^6.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", + "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.210.0.tgz", + "integrity": "sha512-YuaL92Dpyk/Kc1o4e9XiaWWwiC0aBFN+4oy+6A9TP4UNJmRymPMEX10r6EMMFMD7V0hktiSig9cwWo59peeLCQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.210.0", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.1.tgz", + "integrity": "sha512-RKMn3QKi8nE71ULUo0g/MBvq1N4icEBo7cQSKnL3URZT16/YH3nSVgWegOjwx7FRBTrjOIkMJkCUn/ZFIEfn4A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.210.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.210.0.tgz", + "integrity": "sha512-KymqUtYvfpblDNgGxBXYqCcDjYXwjOF7Muc6ocs0rMlG/66Hcs9KiJ7hg4zLOv63JubF/vxi5WXaLrQrPKyaZQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.210.0", + "@opentelemetry/configuration": "0.210.0", + "@opentelemetry/context-async-hooks": "2.4.0", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.210.0", + "@opentelemetry/exporter-logs-otlp-http": "0.210.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.210.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.210.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.210.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.210.0", + "@opentelemetry/exporter-prometheus": "0.210.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.210.0", + "@opentelemetry/exporter-trace-otlp-http": "0.210.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.210.0", + "@opentelemetry/exporter-zipkin": "2.4.0", + "@opentelemetry/instrumentation": "0.210.0", + "@opentelemetry/propagator-b3": "2.4.0", + "@opentelemetry/propagator-jaeger": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/sdk-logs": "0.210.0", + "@opentelemetry/sdk-metrics": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0", + "@opentelemetry/sdk-trace-node": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.4.0.tgz", + "integrity": "sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.4.0.tgz", + "integrity": "sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz", + "integrity": "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.4.0.tgz", + "integrity": "sha512-MBc2l04hZPYygnWPT38UiOPy9ueutPqmJ47z0m9IKuoVQh3MblmbSgwspjhdHagZLfSfmlzhWR1xtbgVNmjX2A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/context-async-hooks": "2.4.0", + "@opentelemetry/core": "2.4.0", + "@opentelemetry/sdk-trace-base": "2.4.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.4.0.tgz", + "integrity": "sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.4.0.tgz", + "integrity": "sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/resources": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.4.0.tgz", + "integrity": "sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz", + "integrity": "sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.4.0", + "@opentelemetry/resources": "2.4.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", + "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.18.0.tgz", + "integrity": "sha512-EhwJNzbfLwQQIeyak3n08EB3UHknMnjy1dFyL98r3xlorje2uzHOT2vkB5nB1zqtTtzT31uSot3oGZFfODbGUg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.18.0.tgz", + "integrity": "sha512-esOPsT9S9B6vEMMp1qR9Yz5UepQXljoWRJYoyp7GV/4SYQOSTpN0+V2fTruxbMmzqLK+fjCEU2x3SVhc96LQLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.18.0.tgz", + "integrity": "sha512-iJknScn8fRLRhGR6VHG31bzOoyLihSDmsJHRjHwRUL0yF1MkLlvzmZ+liKl9MGl+WZkZHaOFT5T1jNlLSWTowQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.18.0.tgz", + "integrity": "sha512-3rMweF2GQLzkaUoWgFKy1fRtk0dpj4JDqucoZLJN9IZG+TC+RZg7QMwG5WKMvmEjzdYmOTw1L1XqZDVXF2ksaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.18.0.tgz", + "integrity": "sha512-TfXsFby4QvpGwmUP66+X+XXQsycddZe9ZUUu/vHhq2XGI1EkparCSzjpYW1Nz5fFncbI5oLymQLln/qR+qxyOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.18.0.tgz", + "integrity": "sha512-WolOILquy9DJsHcfFMHeA5EjTCI9A7JoERFJru4UI2zKZcnfNPo5GApzYwiloscEp/s+fALPmyRntswUns0qHg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.18.0.tgz", + "integrity": "sha512-r+5nHJyPdiBqOGTYAFyuq5RtuAQbm4y69GYWNG/uup9Cqr7RG9Ak0YZgGEbkQsc+XBs00ougu/D1+w3UAYIWHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.18.0.tgz", + "integrity": "sha512-bUzg6QxljqMLLwsxYajAQEHW1LYRLdKOg/aykt14PSqUUOmfnOJjPdSLTiHIZCluVzPCQxv1LjoyRcoTAXfQaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.18.0.tgz", + "integrity": "sha512-l43GVwls5+YR8WXOIez5x7Pp/MfhdkMOZOOjFUSWC/9qMnSLX1kd95j9oxDrkWdD321JdHTyd4eau5KQPxZM9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.18.0.tgz", + "integrity": "sha512-ayj7TweYWi/azxWmRpUZGz41kKNvfkXam20UrFhaQDrSNGNqefQRODxhJn0iv6jt4qChh7TUxDIoavR6ftRsjw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.18.0.tgz", + "integrity": "sha512-2Jz7jpq6BBNlBBup3usZB6sZWEZOBbjWn++/bKC2lpAT+sTEwdTonnf3rNcb+XY7+v53jYB9pM8LEKVXZfr8BA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.18.0.tgz", + "integrity": "sha512-omw8/ISOc6ubR247iEMma4/JRfbY2I+nGJC59oKBhCIEZoyqEg/NmDSBc4ToMH+AsZDucqQUDOCku3k7pBiEag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.18.0.tgz", + "integrity": "sha512-uFipBXaS+honSL5r5G/rlvVrkffUjpKwD3S/aIiwp64bylK3+RztgV+mM1blk+OT5gBRG864auhH6jCfrOo3ZA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.18.0.tgz", + "integrity": "sha512-bY4uMIoKRv8Ine3UiKLFPWRZ+fPCDamTHZFf5pNOjlfmTJIANtJo0mzWDUdFZLYhVgQdegrDL9etZbTMR8qieg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.18.0.tgz", + "integrity": "sha512-40IicL/aitfNOWur06x7Do41WcqFJ9VUNAciFjZCXzF6wR2i6uVsi6N19ecqgSRoLYFCAoRYi9F50QteIxCwKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.18.0.tgz", + "integrity": "sha512-DJIzYjUnSJtz4Trs/J9TnzivtPcUKn9AeL3YjHlM5+RvK27ZL9xISs3gg2VAo2nWU7ThuadC1jSYkWaZyONMwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.18.0.tgz", + "integrity": "sha512-57+R8Ioqc8g9k80WovoupOoyIOfLEceHTizkUcwOXspXLhiZ67ScM7Q8OuvhDoRRSZzH6yI0qML3WZwMFR3s7g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.18.0.tgz", + "integrity": "sha512-t9Oa4BPptJqVlHTT1cV1frs+LY/vjsKhHI6ltj2EwoGM1TykJ0WW43UlQaU4SC8N+oTY8JRbAywVMNkfqjSu9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.18.0.tgz", + "integrity": "sha512-4maf/f6ea5IEtIXqGwSw38srRtVHTre9iKShG4gjzat7c3Iq6B1OppXMj8gNmTuM4n8Xh1hQM9z2hBELccJr1g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.18.0.tgz", + "integrity": "sha512-EhW8Su3AEACSw5HfzKMmyCtV0oArNrVViPdeOfvVYL9TrkL+/4c8fWHFTBtxUMUyCjhSG5xYNdwty1D/TAgL0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", + "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.2.0.tgz", + "integrity": "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.207.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz", + "integrity": "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz", + "integrity": "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.207.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@pydantic/logfire-node": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@pydantic/logfire-node/-/logfire-node-0.13.0.tgz", + "integrity": "sha512-gIU2+06v6tmZChI7yFIEv8+LFmd5OUOw7R5IPgF6s1FCpm6G0QzCxuxiFMXAr8DN+RG3K1sG1tXXqLfq7jQRwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "logfire": "0.13.0", + "picocolors": "^1.1.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.68.0", + "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.210.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.210.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-metrics": "^2.0.0", + "@opentelemetry/sdk-node": "^0.210.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.34.0" + } + }, + "node_modules/@qdrant/js-client-rest": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.17.0.tgz", + "integrity": "sha512-aZFQeirWVqWAa1a8vJ957LMzcXkFHGbsoRhzc8AkGfg6V0jtK8PlG8/eyyc2xhYsR961FDDx1Tx6nyE0K7lS+A==", + "license": "Apache-2.0", + "dependencies": { + "@qdrant/openapi-typescript-fetch": "1.2.6", + "undici": "^6.23.0" + }, + "engines": { + "node": ">=18.17.0", + "pnpm": ">=8" + }, + "peerDependencies": { + "typescript": ">=4.7" + } + }, + "node_modules/@qdrant/openapi-typescript-fetch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.6.tgz", + "integrity": "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.39.0.tgz", + "integrity": "sha512-xCLip2mBwCdRrvXHtVEULX0NffUTYZZBhEUGht0WFL+GNdNQ7gmBOGOczhZlrf2hgFFtDO0fs1xiP9bqq5orEQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.39.0.tgz", + "integrity": "sha512-dx66DtU/xkCTPEDsjU+mYSIEbzu06pzKNQcDA2wvx7wvwsUciZ5yA32Ce/o6p2uHHgy0/joJX9rP5J/BIijaOA==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^2.5.0", + "@opentelemetry/core": "^2.5.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation-amqplib": "0.58.0", + "@opentelemetry/instrumentation-connect": "0.54.0", + "@opentelemetry/instrumentation-dataloader": "0.28.0", + "@opentelemetry/instrumentation-express": "0.59.0", + "@opentelemetry/instrumentation-fs": "0.30.0", + "@opentelemetry/instrumentation-generic-pool": "0.54.0", + "@opentelemetry/instrumentation-graphql": "0.58.0", + "@opentelemetry/instrumentation-hapi": "0.57.0", + "@opentelemetry/instrumentation-http": "0.211.0", + "@opentelemetry/instrumentation-ioredis": "0.59.0", + "@opentelemetry/instrumentation-kafkajs": "0.20.0", + "@opentelemetry/instrumentation-knex": "0.55.0", + "@opentelemetry/instrumentation-koa": "0.59.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.55.0", + "@opentelemetry/instrumentation-mongodb": "0.64.0", + "@opentelemetry/instrumentation-mongoose": "0.57.0", + "@opentelemetry/instrumentation-mysql": "0.57.0", + "@opentelemetry/instrumentation-mysql2": "0.57.0", + "@opentelemetry/instrumentation-pg": "0.63.0", + "@opentelemetry/instrumentation-redis": "0.59.0", + "@opentelemetry/instrumentation-tedious": "0.30.0", + "@opentelemetry/instrumentation-undici": "0.21.0", + "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/semantic-conventions": "^1.39.0", + "@prisma/instrumentation": "7.2.0", + "@sentry/core": "10.39.0", + "@sentry/node-core": "10.39.0", + "@sentry/opentelemetry": "10.39.0", + "import-in-the-middle": "^2.0.6", + "minimatch": "^9.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.39.0.tgz", + "integrity": "sha512-xdeBG00TmtAcGvXnZNbqOCvnZ5kY3s5aT/L8wUQ0w0TT2KmrC9XL/7UHUfJ45TLbjl10kZOtaMQXgUjpwSJW+g==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/tracing-hooks": "^0.3.1", + "@sentry/core": "10.39.0", + "@sentry/opentelemetry": "10.39.0", + "import-in-the-middle": "^2.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/resources": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/context-async-hooks": { + "optional": true + }, + "@opentelemetry/core": { + "optional": true + }, + "@opentelemetry/instrumentation": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/semantic-conventions": { + "optional": true + } + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.58.0.tgz", + "integrity": "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.54.0.tgz", + "integrity": "sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.28.0.tgz", + "integrity": "sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-express": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.59.0.tgz", + "integrity": "sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.30.0.tgz", + "integrity": "sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.54.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.54.0.tgz", + "integrity": "sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.58.0.tgz", + "integrity": "sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.57.0.tgz", + "integrity": "sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.211.0.tgz", + "integrity": "sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.59.0.tgz", + "integrity": "sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.20.0.tgz", + "integrity": "sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.55.0.tgz", + "integrity": "sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.59.0.tgz", + "integrity": "sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.55.0.tgz", + "integrity": "sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.64.0.tgz", + "integrity": "sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.57.0.tgz", + "integrity": "sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.57.0.tgz", + "integrity": "sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.57.0.tgz", + "integrity": "sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.63.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.63.0.tgz", + "integrity": "sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.59.0.tgz", + "integrity": "sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.30.0.tgz", + "integrity": "sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/node/node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.21.0.tgz", + "integrity": "sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@sentry/node/node_modules/minimatch": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.39.0.tgz", + "integrity": "sha512-eU8t/pyxjy7xYt6PNCVxT+8SJw5E3pnupdcUNN4ClqG4O5lX4QCDLtId48ki7i30VqrLtR7vmCHMSvqXXdvXPA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.39.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/archiver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", + "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readdir-glob": "*" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.160", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.160.tgz", + "integrity": "sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/bun": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.9.tgz", + "integrity": "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.3.9" + } + }, + "node_modules/@types/bunyan": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", + "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/memcached": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.10.tgz", + "integrity": "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/oracledb": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-6.5.2.tgz", + "integrity": "sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/agentmail": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/agentmail/-/agentmail-0.1.19.tgz", + "integrity": "sha512-L0au0GnyH24Ob7l0QrkkdwJWL25Aa26f6YnuQlpvwtQMVOTu6IiMftJ8LnVVwQtKwBSZ6cWLS+9SdCvgw+LWGg==", + "dependencies": { + "ws": "^8.16.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "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-or-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-3.0.0.tgz", + "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bun-types": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.9.tgz", + "integrity": "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "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/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_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/collection-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collection-utils/-/collection-utils-1.0.1.tgz", + "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/croner": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/croner/-/croner-10.0.1.tgz", + "integrity": "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==", + "funding": [ + { + "type": "other", + "url": "https://paypal.me/hexagonpp" + }, + { + "type": "github", + "url": "https://github.com/sponsors/hexagon" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drizzle-kit": { + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz", + "integrity": "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.19.7", + "esbuild-register": "^3.5.0", + "gel": "^2.0.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.38.4.tgz", + "integrity": "sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/react": ">=18", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "react": ">=18", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.1", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.5.3.tgz", + "integrity": "sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^7.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/fast-copy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", + "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-blob/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "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", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatbuffers": { + "version": "25.9.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", + "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", + "license": "Apache-2.0" + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gel": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gel/-/gel-2.2.0.tgz", + "integrity": "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@petamoriken/float16": "^3.8.7", + "debug": "^4.3.4", + "env-paths": "^3.0.0", + "semver": "^7.6.2", + "shell-quote": "^1.8.1", + "which": "^4.0.0" + }, + "bin": { + "gel": "dist/cli.mjs" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/gel/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/gel/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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", + "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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gtoken/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gtoken/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/knip": { + "version": "5.85.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.85.0.tgz", + "integrity": "sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "@nodelib/fs.walk": "^1.2.3", + "fast-glob": "^3.3.3", + "formatly": "^0.3.0", + "jiti": "^2.6.0", + "js-yaml": "^4.1.1", + "minimist": "^1.2.8", + "oxc-resolver": "^11.15.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.1", + "smol-toml": "^1.5.2", + "strip-json-comments": "5.0.3", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4 <7" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/logfire": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/logfire/-/logfire-0.13.0.tgz", + "integrity": "sha512-A2Q1jQXRrL2el5JFLlqaF8UNs2RxmDGqPM3iECH1wAT53KiPXJj9EsuCPVsqj3Xpj2gSEvMrQ6eVwZ4evSWSGQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.34.0" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "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==", + "license": "ISC" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onnxruntime-common": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz", + "integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==", + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.21.0.tgz", + "integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==", + "hasInstallScript": true, + "license": "MIT", + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "global-agent": "^3.0.0", + "onnxruntime-common": "1.21.0", + "tar": "^7.0.1" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.22.0-dev.20250409-89f8206ba4", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.22.0-dev.20250409-89f8206ba4.tgz", + "integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, + "node_modules/onnxruntime-web/node_modules/onnxruntime-common": { + "version": "1.22.0-dev.20250409-89f8206ba4", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.22.0-dev.20250409-89f8206ba4.tgz", + "integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==", + "license": "MIT" + }, + "node_modules/openai": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.23.0.tgz", + "integrity": "sha512-w6NJofZ12lUQLm5W8RJcqq0HhGE4gZuqVFrBA1q40qx0Uyn/kcrSbOY542C2WHtyTZLz9ucNr4WUO46m8r43YQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/oxc-resolver": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.18.0.tgz", + "integrity": "sha512-Fv/b05AfhpYoCDvsog6tgsDm2yIwIeJafpMFLncNwKHRYu+Y1xQu5Q/rgUn7xBfuhNgjtPO7C0jCf7p2fLDj1g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.18.0", + "@oxc-resolver/binding-android-arm64": "11.18.0", + "@oxc-resolver/binding-darwin-arm64": "11.18.0", + "@oxc-resolver/binding-darwin-x64": "11.18.0", + "@oxc-resolver/binding-freebsd-x64": "11.18.0", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.18.0", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.18.0", + "@oxc-resolver/binding-linux-arm64-gnu": "11.18.0", + "@oxc-resolver/binding-linux-arm64-musl": "11.18.0", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.18.0", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.18.0", + "@oxc-resolver/binding-linux-riscv64-musl": "11.18.0", + "@oxc-resolver/binding-linux-s390x-gnu": "11.18.0", + "@oxc-resolver/binding-linux-x64-gnu": "11.18.0", + "@oxc-resolver/binding-linux-x64-musl": "11.18.0", + "@oxc-resolver/binding-openharmony-arm64": "11.18.0", + "@oxc-resolver/binding-wasm32-wasi": "11.18.0", + "@oxc-resolver/binding-win32-arm64-msvc": "11.18.0", + "@oxc-resolver/binding-win32-ia32-msvc": "11.18.0", + "@oxc-resolver/binding-win32-x64-msvc": "11.18.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^4.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.8.tgz", + "integrity": "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==", + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/quicktype-core": { + "version": "23.2.6", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.2.6.tgz", + "integrity": "sha512-asfeSv7BKBNVb9WiYhFRBvBZHcRutPRBwJMxW0pefluK4kkKu4lv0IvZBwFKvw2XygLcL1Rl90zxWDHYgkwCmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@glideapps/ts-necessities": "2.2.3", + "browser-or-node": "^3.0.0", + "collection-utils": "^1.0.1", + "cross-fetch": "^4.0.0", + "is-url": "^1.2.4", + "js-base64": "^3.7.7", + "lodash": "^4.17.21", + "pako": "^1.0.6", + "pluralize": "^8.0.0", + "readable-stream": "4.5.2", + "unicode-properties": "^1.4.1", + "urijs": "^1.19.1", + "wordwrap": "^1.0.0", + "yaml": "^2.4.1" + } + }, + "node_modules/quicktype-core/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", + "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rrule": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", + "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT" + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "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==", + "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/string-width-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", + "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", + "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.23" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", + "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "license": "MIT" + }, + "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/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-sitter-bash": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz", + "integrity": "sha512-7hMytuYIMoXOq24yRulgIxthE9YmggZIOHCyPTTuJcu6EU54tYD+4G39cUb28kxC6jMf/AbPfWGLQtgPTdh3xw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-json-schema": { + "version": "0.67.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.67.1.tgz", + "integrity": "sha512-vKTZB/RoYTIBdVP7E7vrgHMCssBuhja91wQy498QIVhvfRimaOgjc98uwAXmZ7mbLUytJmOSbF11wPz+ByQeXg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^18.11.9", + "glob": "^7.1.7", + "path-equal": "^1.2.5", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~5.5.0", + "vm2": "^3.10.0", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/typescript-json-schema/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript-json-schema/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/typescript-json-schema/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript-json-schema/node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-json-schema/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vm2": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.10.5.tgz", + "integrity": "sha512-3P/2QDccVFBcujfCOeP8vVNuGfuBJHEuvGR8eMmI10p/iwLL2UwF5PDaNaoOS2pRGQEDmJRyeEcc8kmm2Z59RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "acorn-walk": "^8.3.4" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.26.5", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.5.tgz", + "integrity": "sha512-u9sl+q21VSKX2T8dhpQw8bMGGqNfwaIyuoYE3kdOQGVDrOqrmcS9GmaQoCS602iaFnuokn3WCHW374c7GAnuaQ==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "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==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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==", + "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/wrap-ansi-cjs/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "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/wrap-ansi-cjs/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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "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/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/assistant/scripts/ipc/generate-swift.ts b/assistant/scripts/ipc/generate-swift.ts index 03ee8930d7c..5c93994c2eb 100644 --- a/assistant/scripts/ipc/generate-swift.ts +++ b/assistant/scripts/ipc/generate-swift.ts @@ -196,6 +196,7 @@ const INT_PATTERNS = [ /[Ii]ndex$/, /[Ee]xpected$/, /[Uu]ndos$/, + /Ms$/, ]; function shouldBeInt(propName: string): boolean { diff --git a/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap b/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap index 30b4f00e782..471f3f1fdd7 100644 --- a/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +++ b/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap @@ -37,7 +37,7 @@ exports[`IPC message snapshots ClientMessage types session_create serializes to "threadType": "standard", "title": "New session", "transport": { - "channelId": "desktop", + "channelId": "macos", "hints": [ "dashboard-capable", ], @@ -146,6 +146,32 @@ exports[`IPC message snapshots ClientMessage types cu_session_abort serializes t } `; +exports[`IPC message snapshots ClientMessage types cu_auto_approve_update serializes to expected JSON 1`] = ` +{ + "enabled": true, + "sessionId": "cu-sess-001", + "type": "cu_auto_approve_update", +} +`; + +exports[`IPC message snapshots ClientMessage types cu_recording_status serializes to expected JSON 1`] = ` +{ + "sessionId": "cu-sess-001", + "status": "started", + "type": "cu_recording_status", +} +`; + +exports[`IPC message snapshots ClientMessage types cu_session_finalized serializes to expected JSON 1`] = ` +{ + "sessionId": "cu-sess-001", + "status": "completed", + "stepCount": 5, + "summary": "Task completed successfully", + "type": "cu_session_finalized", +} +`; + exports[`IPC message snapshots ClientMessage types cu_observation serializes to expected JSON 1`] = ` { "axDiff": "+ new element", @@ -196,6 +222,7 @@ exports[`IPC message snapshots ClientMessage types watch_observation serializes exports[`IPC message snapshots ClientMessage types task_submit serializes to expected JSON 1`] = ` { + "conversationId": "conv-001", "screenHeight": 1080, "screenWidth": 1920, "task": "Open Safari and search for weather", @@ -620,8 +647,7 @@ exports[`IPC message snapshots ClientMessage types twilio_config serializes to e exports[`IPC message snapshots ClientMessage types channel_readiness serializes to expected JSON 1`] = ` { "action": "get", - "channel": "sms", - "includeRemote": true, + "channel": "telegram", "type": "channel_readiness", } `; @@ -1431,7 +1457,7 @@ exports[`IPC message snapshots ServerMessage types task_routed serializes to exp exports[`IPC message snapshots ServerMessage types ride_shotgun_progress serializes to expected JSON 1`] = ` { - "message": "Observing user activity...", + "message": "Analyzing screen content...", "type": "ride_shotgun_progress", "watchId": "watch-shotgun-001", } @@ -2043,32 +2069,9 @@ exports[`IPC message snapshots ServerMessage types telegram_config_response seri exports[`IPC message snapshots ServerMessage types twilio_config_response serializes to expected JSON 1`] = ` { - "compliance": { - "numberType": "toll_free", - "verificationSid": "TF_VER_001", - "verificationStatus": "TWILIO_APPROVED", - }, - "diagnostics": { - "actionItems": [], - "compliance": { - "detail": "Toll-free verification: TWILIO_APPROVED", - "status": "TWILIO_APPROVED", - }, - "overallStatus": "healthy", - "readiness": { - "issues": [], - "ready": true, - }, - }, "hasCredentials": true, "phoneNumber": "+15551234567", "success": true, - "testResult": { - "finalStatus": "delivered", - "initialStatus": "queued", - "messageSid": "SM-test-001", - "to": "+15559876543", - }, "type": "twilio_config_response", } `; @@ -2077,32 +2080,17 @@ exports[`IPC message snapshots ServerMessage types channel_readiness_response se { "snapshots": [ { - "channel": "sms", - "checkedAt": 1700000000000, + "channel": "telegram", + "checkedAt": 1700000000, "localChecks": [ { - "message": "Twilio credentials are not configured", - "name": "twilio_credentials", - "passed": false, - }, - { - "message": "Phone number is assigned", - "name": "phone_number", - "passed": true, - }, - { - "message": "Public ingress URL is configured", - "name": "ingress", + "message": "Bot token configured", + "name": "bot_token", "passed": true, }, ], - "ready": false, - "reasons": [ - { - "code": "twilio_credentials", - "text": "Twilio credentials are not configured", - }, - ], + "ready": true, + "reasons": [], "stale": false, }, ], diff --git a/assistant/src/__tests__/attachment-content-route.test.ts b/assistant/src/__tests__/attachment-content-route.test.ts new file mode 100644 index 00000000000..48d65211559 --- /dev/null +++ b/assistant/src/__tests__/attachment-content-route.test.ts @@ -0,0 +1,236 @@ +import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test'; +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const testDir = mkdtempSync(join(tmpdir(), 'attach-content-test-')); + +mock.module('../util/platform.js', () => ({ + getDataDir: () => testDir, + isMacOS: () => process.platform === 'darwin', + isLinux: () => process.platform === 'linux', + isWindows: () => process.platform === 'win32', + getSocketPath: () => join(testDir, 'test.sock'), + getPidPath: () => join(testDir, 'test.pid'), + getDbPath: () => join(testDir, 'test.db'), + getLogPath: () => join(testDir, 'test.log'), + ensureDataDir: () => {}, + getRootDir: () => testDir, +})); + +mock.module('../util/logger.js', () => ({ + getLogger: () => new Proxy({} as Record, { + get: () => () => {}, + }), +})); + +mock.module('../config/loader.js', () => ({ + getConfig: () => ({ + model: 'test', + provider: 'test', + apiKeys: {}, + memory: { enabled: false }, + rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 }, + }), +})); + +import { initializeDb, getDb, resetDb } from '../memory/db.js'; +import { + uploadAttachment, + createFileBackedAttachment, +} from '../memory/attachments-store.js'; +import { handleGetAttachmentContent } from '../runtime/routes/attachment-routes.js'; + +initializeDb(); + +afterAll(() => { + resetDb(); + try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ } +}); + +function resetTables() { + const db = getDb(); + db.run('DELETE FROM message_attachments'); + db.run('DELETE FROM attachments'); +} + +// --------------------------------------------------------------------------- +// handleGetAttachmentContent — full file content +// --------------------------------------------------------------------------- + +describe('handleGetAttachmentContent — file-backed', () => { + beforeEach(resetTables); + + test('returns full file content for non-range request', async () => { + const filePath = join(testDir, 'test-video.mp4'); + const content = Buffer.from('fake video content for testing'); + writeFileSync(filePath, content); + + const attachment = createFileBackedAttachment({ + filename: 'test-video.mp4', + mimeType: 'video/mp4', + sizeBytes: content.length, + filePath, + }); + + const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content'); + const res = handleGetAttachmentContent(attachment.id, req); + + expect(res.status).toBe(200); + expect(res.headers.get('Content-Type')).toBe('video/mp4'); + expect(res.headers.get('Content-Length')).toBe(String(content.length)); + expect(res.headers.get('Accept-Ranges')).toBe('bytes'); + expect(res.headers.get('Content-Disposition')).toBe('inline'); + + const body = await res.arrayBuffer(); + expect(Buffer.from(body).toString()).toBe(content.toString()); + }); + + test('returns partial content for range request', async () => { + const filePath = join(testDir, 'test-range.mp4'); + const content = Buffer.from('0123456789abcdef'); + writeFileSync(filePath, content); + + const attachment = createFileBackedAttachment({ + filename: 'test-range.mp4', + mimeType: 'video/mp4', + sizeBytes: content.length, + filePath, + }); + + const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content', { + headers: { Range: 'bytes=4-9' }, + }); + const res = handleGetAttachmentContent(attachment.id, req); + + expect(res.status).toBe(206); + expect(res.headers.get('Content-Range')).toBe(`bytes 4-9/${content.length}`); + expect(res.headers.get('Content-Length')).toBe('6'); + + const body = await res.arrayBuffer(); + expect(Buffer.from(body).toString()).toBe('456789'); + }); + + test('returns range with open-ended range header', async () => { + const filePath = join(testDir, 'test-open-range.mp4'); + const content = Buffer.from('abcdefghij'); + writeFileSync(filePath, content); + + const attachment = createFileBackedAttachment({ + filename: 'test-open-range.mp4', + mimeType: 'video/mp4', + sizeBytes: content.length, + filePath, + }); + + const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content', { + headers: { Range: 'bytes=5-' }, + }); + const res = handleGetAttachmentContent(attachment.id, req); + + expect(res.status).toBe(206); + expect(res.headers.get('Content-Range')).toBe(`bytes 5-9/${content.length}`); + expect(res.headers.get('Content-Length')).toBe('5'); + + const body = await res.arrayBuffer(); + expect(Buffer.from(body).toString()).toBe('fghij'); + }); + + test('returns 416 when start is beyond file size', () => { + const filePath = join(testDir, 'test-416.mp4'); + const content = Buffer.from('short'); + writeFileSync(filePath, content); + + const attachment = createFileBackedAttachment({ + filename: 'test-416.mp4', + mimeType: 'video/mp4', + sizeBytes: content.length, + filePath, + }); + + const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content', { + headers: { Range: 'bytes=100-200' }, + }); + const res = handleGetAttachmentContent(attachment.id, req); + + expect(res.status).toBe(416); + }); + + test('clamps oversized end to fileSize-1 per RFC 7233', async () => { + const filePath = join(testDir, 'test-clamp.mp4'); + const content = Buffer.from('0123456789'); + writeFileSync(filePath, content); + + const attachment = createFileBackedAttachment({ + filename: 'test-clamp.mp4', + mimeType: 'video/mp4', + sizeBytes: content.length, + filePath, + }); + + // Request bytes=5-999 on a 10-byte file; end should be clamped to 9 + const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content', { + headers: { Range: 'bytes=5-999' }, + }); + const res = handleGetAttachmentContent(attachment.id, req); + + expect(res.status).toBe(206); + expect(res.headers.get('Content-Range')).toBe(`bytes 5-9/${content.length}`); + expect(res.headers.get('Content-Length')).toBe('5'); + + const body = await res.arrayBuffer(); + expect(Buffer.from(body).toString()).toBe('56789'); + }); + + test('returns 404 when file is missing from disk', () => { + const attachment = createFileBackedAttachment({ + filename: 'missing.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/nonexistent/path/missing.mp4', + }); + + const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content'); + const res = handleGetAttachmentContent(attachment.id, req); + + expect(res.status).toBe(404); + }); +}); + +// --------------------------------------------------------------------------- +// handleGetAttachmentContent — 404 for non-existent +// --------------------------------------------------------------------------- + +describe('handleGetAttachmentContent — 404', () => { + beforeEach(resetTables); + + test('returns 404 for non-existent attachment', () => { + const req = new Request('http://localhost/v1/attachments/nonexistent/content'); + const res = handleGetAttachmentContent('nonexistent', req); + expect(res.status).toBe(404); + }); +}); + +// --------------------------------------------------------------------------- +// handleGetAttachmentContent — inline_base64 fallback +// --------------------------------------------------------------------------- + +describe('handleGetAttachmentContent — inline_base64 fallback', () => { + beforeEach(resetTables); + + test('decodes and returns inline base64 content', async () => { + const originalText = 'hello world'; + const base64 = Buffer.from(originalText).toString('base64'); + const stored = uploadAttachment('hello.txt', 'text/plain', base64); + + const req = new Request('http://localhost/v1/attachments/' + stored.id + '/content'); + const res = handleGetAttachmentContent(stored.id, req); + + expect(res.status).toBe(200); + expect(res.headers.get('Content-Type')).toBe('text/plain'); + expect(res.headers.get('Accept-Ranges')).toBe('bytes'); + + const body = await res.text(); + expect(body).toBe(originalText); + }); +}); diff --git a/assistant/src/__tests__/cu-session-finalized.test.ts b/assistant/src/__tests__/cu-session-finalized.test.ts new file mode 100644 index 00000000000..8480245edcd --- /dev/null +++ b/assistant/src/__tests__/cu-session-finalized.test.ts @@ -0,0 +1,330 @@ +import { describe, test, expect, mock } from 'bun:test'; +import * as net from 'node:net'; + +// Mock the conversation store before importing the handler. +const mockConversations = new Map(); +const mockAddedMessages: Array<{ + conversationId: string; + role: string; + content: string; + metadata?: Record; +}> = []; + +mock.module('../memory/conversation-store.js', () => ({ + getConversation: (id: string) => mockConversations.get(id) ?? null, + addMessage: ( + conversationId: string, + role: string, + content: string, + metadata?: Record, + ) => { + const msg = { conversationId, role, content, metadata }; + mockAddedMessages.push(msg); + return { id: 'mock-msg-id', conversationId, role, content, createdAt: Date.now() }; + }, +})); + +// Mock the config loader (required by shared.ts transitively). +mock.module('../config/loader.js', () => ({ + getConfig: () => ({ + provider: 'mock-provider', + permissions: { mode: 'legacy' }, + apiKeys: {}, + sandbox: { enabled: false }, + timeouts: { toolExecutionTimeoutSec: 30, permissionTimeoutSec: 5 }, + skills: { load: { extraDirs: [] } }, + secretDetection: { enabled: false }, + memory: {}, + rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 }, + }), + invalidateConfigCache: () => {}, +})); + +import type { CuSessionFinalized, ServerMessage } from '../daemon/ipc-contract.js'; +import type { HandlerContext, CuSessionMetadata } from '../daemon/handlers/shared.js'; +import { handleCuSessionFinalized } from '../daemon/handlers/computer-use.js'; +import type { ComputerUseSession } from '../daemon/computer-use-session.js'; + +/** Create a minimal HandlerContext for testing. */ +function makeCtx(overrides?: Partial): HandlerContext { + return { + sessions: new Map(), + socketToSession: new Map(), + cuSessions: new Map(), + socketToCuSession: new Map(), + cuSessionMetadata: new Map(), + cuObservationParseSequence: new Map(), + socketSandboxOverride: new Map(), + sharedRequestTimestamps: [], + debounceTimers: new Map() as unknown as HandlerContext['debounceTimers'], + suppressConfigReload: false, + setSuppressConfigReload: () => {}, + updateConfigFingerprint: () => {}, + send: () => {}, + broadcast: () => {}, + clearAllSessions: () => 0, + getOrCreateSession: async () => { throw new Error('not implemented in test'); }, + touchSession: () => {}, + ...overrides, + }; +} + +describe('handleCuSessionFinalized', () => { + test('injects summary into reporting session when reportToSessionId is set', () => { + // Set up a reporting session conversation. + const reportSessionId = 'report-session-1'; + mockConversations.set(reportSessionId, { id: reportSessionId }); + mockAddedMessages.length = 0; + + // Create a mock socket for the reporting session. + const reportSocket = new net.Socket(); + const sentMessages: Array<{ socket: net.Socket; msg: ServerMessage }> = []; + + const ctx = makeCtx({ + send: (socket: net.Socket, msg: ServerMessage) => { sentMessages.push({ socket, msg }); }, + socketToSession: new Map([[reportSocket, reportSessionId]]), + }); + + // Simulate a CU session with metadata. + const cuSessionId = 'cu-session-1'; + ctx.cuSessions.set(cuSessionId, {} as ComputerUseSession); + ctx.cuSessionMetadata.set(cuSessionId, { + reportToSessionId: reportSessionId, + qaMode: true, + }); + + const msg: CuSessionFinalized = { + type: 'cu_session_finalized', + sessionId: cuSessionId, + status: 'completed', + summary: 'QA test passed: login flow works correctly.', + stepCount: 5, + }; + + handleCuSessionFinalized(msg, new net.Socket(), ctx); + + // Verify the assistant message was persisted. + expect(mockAddedMessages.length).toBe(1); + expect(mockAddedMessages[0].conversationId).toBe(reportSessionId); + expect(mockAddedMessages[0].role).toBe('assistant'); + const parsedContent = JSON.parse(mockAddedMessages[0].content); + expect(parsedContent).toEqual([{ type: 'text', text: msg.summary }]); + expect(mockAddedMessages[0].metadata).toMatchObject({ + source: 'cu_session_finalized', + cuSessionId, + cuStatus: 'completed', + cuStepCount: 5, + qaMode: true, + }); + + // Verify IPC messages were sent to the reporting socket. + expect(sentMessages.length).toBe(2); + expect(sentMessages[0].msg).toMatchObject({ + type: 'assistant_text_delta', + text: msg.summary, + sessionId: reportSessionId, + }); + expect(sentMessages[1].msg).toMatchObject({ + type: 'message_complete', + sessionId: reportSessionId, + }); + expect(sentMessages[0].socket).toBe(reportSocket); + + // Verify CU session state was cleaned up. + expect(ctx.cuSessions.has(cuSessionId)).toBe(false); + expect(ctx.cuSessionMetadata.has(cuSessionId)).toBe(false); + }); + + test('cleans up CU session state even without reportToSessionId', () => { + const ctx = makeCtx(); + const cuSessionId = 'cu-session-2'; + ctx.cuSessions.set(cuSessionId, {} as ComputerUseSession); + // No metadata set — this is a non-QA CU session. + + const msg: CuSessionFinalized = { + type: 'cu_session_finalized', + sessionId: cuSessionId, + status: 'completed', + summary: 'Task done.', + stepCount: 3, + }; + + mockAddedMessages.length = 0; + handleCuSessionFinalized(msg, new net.Socket(), ctx); + + // No message should be persisted (no reportToSessionId). + expect(mockAddedMessages.length).toBe(0); + + // CU session state should still be cleaned up. + expect(ctx.cuSessions.has(cuSessionId)).toBe(false); + }); + + test('handles missing reporting conversation gracefully', () => { + const ctx = makeCtx(); + const cuSessionId = 'cu-session-3'; + ctx.cuSessions.set(cuSessionId, {} as ComputerUseSession); + ctx.cuSessionMetadata.set(cuSessionId, { + reportToSessionId: 'nonexistent-session', + qaMode: true, + }); + + // Make sure the conversation does NOT exist in the mock store. + mockConversations.delete('nonexistent-session'); + mockAddedMessages.length = 0; + + const msg: CuSessionFinalized = { + type: 'cu_session_finalized', + sessionId: cuSessionId, + status: 'failed', + summary: 'QA test failed.', + stepCount: 2, + }; + + // Should not throw even if the conversation is missing. + handleCuSessionFinalized(msg, new net.Socket(), ctx); + + expect(mockAddedMessages.length).toBe(0); + expect(ctx.cuSessions.has(cuSessionId)).toBe(false); + expect(ctx.cuSessionMetadata.has(cuSessionId)).toBe(false); + }); + + test('logs recording metadata without crashing', () => { + const reportSessionId = 'report-session-rec'; + mockConversations.set(reportSessionId, { id: reportSessionId }); + mockAddedMessages.length = 0; + + const ctx = makeCtx(); + const cuSessionId = 'cu-session-rec'; + ctx.cuSessions.set(cuSessionId, {} as ComputerUseSession); + ctx.cuSessionMetadata.set(cuSessionId, { + reportToSessionId: reportSessionId, + }); + + const msg: CuSessionFinalized = { + type: 'cu_session_finalized', + sessionId: cuSessionId, + status: 'completed', + summary: 'Done with recording.', + stepCount: 4, + recording: { + localPath: '/tmp/recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 1024000, + durationMs: 30000, + width: 1920, + height: 1080, + captureScope: 'window', + includeAudio: false, + }, + }; + + handleCuSessionFinalized(msg, new net.Socket(), ctx); + + // Message should be persisted with recording path in metadata. + expect(mockAddedMessages.length).toBe(1); + expect(mockAddedMessages[0].metadata).toMatchObject({ + recordingPath: '/tmp/recording.mp4', + }); + }); + + test('creates fallback attachment when reportToSessionId exists but conversation is missing and recording is present', () => { + // This tests the edge case where reportToSessionId is set but the conversation + // doesn't exist (e.g. it was deleted or never created). The recording should + // still get a file-backed attachment entry so cleanup can track it. + const ctx = makeCtx(); + const cuSessionId = 'cu-session-orphan-with-report'; + ctx.cuSessions.set(cuSessionId, {} as ComputerUseSession); + ctx.cuSessionMetadata.set(cuSessionId, { + reportToSessionId: 'nonexistent-session', + qaMode: true, + }); + + // Ensure the conversation does NOT exist. + mockConversations.delete('nonexistent-session'); + mockAddedMessages.length = 0; + + const msg: CuSessionFinalized = { + type: 'cu_session_finalized', + sessionId: cuSessionId, + status: 'completed', + summary: 'QA recording completed.', + stepCount: 3, + recording: { + localPath: '/tmp/orphan-recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 2048000, + durationMs: 45000, + width: 1920, + height: 1080, + captureScope: 'window', + includeAudio: false, + }, + }; + + // Should not throw. + handleCuSessionFinalized(msg, new net.Socket(), ctx); + + // No message persisted (conversation missing), but the recording should + // still be tracked. We can't easily verify createFileBackedAttachment + // was called without mocking it, but at minimum the function shouldn't throw + // and state should be cleaned up. + expect(mockAddedMessages.length).toBe(0); + expect(ctx.cuSessions.has(cuSessionId)).toBe(false); + expect(ctx.cuSessionMetadata.has(cuSessionId)).toBe(false); + }); + + test('creates fallback attachment when summary is empty but recording is present', () => { + const ctx = makeCtx(); + const cuSessionId = 'cu-session-empty-summary'; + ctx.cuSessions.set(cuSessionId, {} as ComputerUseSession); + ctx.cuSessionMetadata.set(cuSessionId, { + reportToSessionId: 'some-session', + qaMode: true, + }); + + mockConversations.set('some-session', { id: 'some-session' }); + mockAddedMessages.length = 0; + + const msg: CuSessionFinalized = { + type: 'cu_session_finalized', + sessionId: cuSessionId, + status: 'completed', + summary: '', // Empty summary — injection path skipped + stepCount: 2, + recording: { + localPath: '/tmp/empty-summary-recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 512000, + durationMs: 10000, + width: 1280, + height: 720, + captureScope: 'display', + includeAudio: true, + }, + }; + + handleCuSessionFinalized(msg, new net.Socket(), ctx); + + // No message persisted (empty summary skips injection path). + expect(mockAddedMessages.length).toBe(0); + // State cleaned up. + expect(ctx.cuSessions.has(cuSessionId)).toBe(false); + expect(ctx.cuSessionMetadata.has(cuSessionId)).toBe(false); + }); + + test('stores and retrieves CU session metadata', () => { + const ctx = makeCtx(); + + const cuSessionId = 'cu-meta-test'; + const meta: CuSessionMetadata = { + reportToSessionId: 'parent-123', + qaMode: true, + }; + ctx.cuSessionMetadata.set(cuSessionId, meta); + + const stored = ctx.cuSessionMetadata.get(cuSessionId); + expect(stored).toEqual(meta); + expect(stored?.reportToSessionId).toBe('parent-123'); + expect(stored?.qaMode).toBe(true); + }); +}); diff --git a/assistant/src/__tests__/file-backed-attachments.test.ts b/assistant/src/__tests__/file-backed-attachments.test.ts new file mode 100644 index 00000000000..06ed23015de --- /dev/null +++ b/assistant/src/__tests__/file-backed-attachments.test.ts @@ -0,0 +1,319 @@ +import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const testDir = mkdtempSync(join(tmpdir(), 'file-backed-attach-test-')); + +mock.module('../util/platform.js', () => ({ + getDataDir: () => testDir, + isMacOS: () => process.platform === 'darwin', + isLinux: () => process.platform === 'linux', + isWindows: () => process.platform === 'win32', + getSocketPath: () => join(testDir, 'test.sock'), + getPidPath: () => join(testDir, 'test.pid'), + getDbPath: () => join(testDir, 'test.db'), + getLogPath: () => join(testDir, 'test.log'), + ensureDataDir: () => {}, + getRootDir: () => testDir, +})); + +mock.module('../util/logger.js', () => ({ + getLogger: () => new Proxy({} as Record, { + get: () => () => {}, + }), +})); + +mock.module('../config/loader.js', () => ({ + getConfig: () => ({ + model: 'test', + provider: 'test', + apiKeys: {}, + memory: { enabled: false }, + rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 }, + }), +})); + +import { initializeDb, getDb, resetDb } from '../memory/db.js'; +import { + uploadAttachment, + getAttachmentById, + getAttachmentsByIds, + createFileBackedAttachment, + getExpiredFileAttachments, + deleteFileBackedAttachment, +} from '../memory/attachments-store.js'; + +initializeDb(); + +afterAll(() => { + resetDb(); + try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ } +}); + +function resetTables() { + const db = getDb(); + db.run('DELETE FROM message_attachments'); + db.run('DELETE FROM attachments'); +} + +// --------------------------------------------------------------------------- +// createFileBackedAttachment +// --------------------------------------------------------------------------- + +describe('createFileBackedAttachment', () => { + beforeEach(resetTables); + + test('creates correct DB record with file metadata', () => { + const result = createFileBackedAttachment({ + filename: 'recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 50_000_000, + filePath: '/data/recordings/recording.mp4', + sha256: 'abc123def456', + expiresAt: Date.now() + 86400000, + }); + + expect(result.id).toBeDefined(); + expect(result.originalFilename).toBe('recording.mp4'); + expect(result.mimeType).toBe('video/mp4'); + expect(result.sizeBytes).toBe(50_000_000); + expect(result.kind).toBe('video'); + expect(result.storageKind).toBe('file'); + expect(result.filePath).toBe('/data/recordings/recording.mp4'); + expect(result.sha256).toBe('abc123def456'); + expect(result.expiresAt).toBeGreaterThan(0); + expect(result.createdAt).toBeGreaterThan(0); + }); + + test('handles optional fields gracefully', () => { + const result = createFileBackedAttachment({ + filename: 'screenshot.png', + mimeType: 'image/png', + sizeBytes: 1024, + filePath: '/data/screenshots/shot.png', + }); + + expect(result.sha256).toBeNull(); + expect(result.expiresAt).toBeNull(); + expect(result.thumbnailBase64).toBeNull(); + }); + + test('stores thumbnail when provided', () => { + const result = createFileBackedAttachment({ + filename: 'video.mp4', + mimeType: 'video/mp4', + sizeBytes: 10_000, + filePath: '/data/video.mp4', + thumbnailBase64: 'iVBORw0KGgoAAAANSUh', + }); + + expect(result.thumbnailBase64).toBe('iVBORw0KGgoAAAANSUh'); + }); + + test('classifies kind from mime type', () => { + const image = createFileBackedAttachment({ + filename: 'pic.png', + mimeType: 'image/png', + sizeBytes: 100, + filePath: '/data/pic.png', + }); + expect(image.kind).toBe('image'); + + const video = createFileBackedAttachment({ + filename: 'clip.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/data/clip.mp4', + }); + expect(video.kind).toBe('video'); + + const doc = createFileBackedAttachment({ + filename: 'doc.pdf', + mimeType: 'application/pdf', + sizeBytes: 100, + filePath: '/data/doc.pdf', + }); + expect(doc.kind).toBe('document'); + }); +}); + +// --------------------------------------------------------------------------- +// getAttachmentById returns file metadata for file-backed attachments +// --------------------------------------------------------------------------- + +describe('getAttachmentById with file-backed attachments', () => { + beforeEach(resetTables); + + test('returns file metadata for file-backed attachments', () => { + const created = createFileBackedAttachment({ + filename: 'recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 50_000_000, + filePath: '/data/recording.mp4', + sha256: 'abc123', + expiresAt: 1234567890, + }); + + const fetched = getAttachmentById(created.id); + expect(fetched).not.toBeNull(); + expect(fetched!.storageKind).toBe('file'); + expect(fetched!.filePath).toBe('/data/recording.mp4'); + expect(fetched!.sha256).toBe('abc123'); + expect(fetched!.expiresAt).toBe(1234567890); + expect(fetched!.dataBase64).toBe(''); + }); + + test('returns inline_base64 for traditional attachments', () => { + const created = uploadAttachment('chart.png', 'image/png', 'iVBORw0K'); + + const fetched = getAttachmentById(created.id); + expect(fetched).not.toBeNull(); + expect(fetched!.storageKind).toBe('inline_base64'); + expect(fetched!.filePath).toBeNull(); + expect(fetched!.sha256).toBeNull(); + expect(fetched!.expiresAt).toBeNull(); + expect(fetched!.dataBase64).toBe('iVBORw0K'); + }); +}); + +// --------------------------------------------------------------------------- +// getAttachmentsByIds with mixed types +// --------------------------------------------------------------------------- + +describe('getAttachmentsByIds with mixed types', () => { + beforeEach(resetTables); + + test('returns both inline and file-backed attachments', () => { + const inline = uploadAttachment('doc.pdf', 'application/pdf', 'JVBER'); + const fileBacked = createFileBackedAttachment({ + filename: 'video.mp4', + mimeType: 'video/mp4', + sizeBytes: 5000, + filePath: '/data/video.mp4', + }); + + const results = getAttachmentsByIds([inline.id, fileBacked.id]); + expect(results).toHaveLength(2); + + const inlineResult = results.find((r) => r.id === inline.id); + expect(inlineResult!.storageKind).toBe('inline_base64'); + expect(inlineResult!.dataBase64).toBe('JVBER'); + + const fileResult = results.find((r) => r.id === fileBacked.id); + expect(fileResult!.storageKind).toBe('file'); + expect(fileResult!.filePath).toBe('/data/video.mp4'); + }); +}); + +// --------------------------------------------------------------------------- +// getExpiredFileAttachments +// --------------------------------------------------------------------------- + +describe('getExpiredFileAttachments', () => { + beforeEach(resetTables); + + test('returns only expired file attachments', () => { + const now = Date.now(); + + // Expired + createFileBackedAttachment({ + filename: 'old.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/data/old.mp4', + expiresAt: now - 10000, + }); + + // Not expired + createFileBackedAttachment({ + filename: 'new.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/data/new.mp4', + expiresAt: now + 86400000, + }); + + // No expiry + createFileBackedAttachment({ + filename: 'permanent.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/data/permanent.mp4', + }); + + // Inline base64 (should never be returned) + uploadAttachment('inline.txt', 'text/plain', 'AAAA'); + + const expired = getExpiredFileAttachments(); + expect(expired).toHaveLength(1); + expect(expired[0].filePath).toBe('/data/old.mp4'); + }); + + test('returns empty when no expired attachments', () => { + createFileBackedAttachment({ + filename: 'future.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/data/future.mp4', + expiresAt: Date.now() + 86400000, + }); + + const expired = getExpiredFileAttachments(); + expect(expired).toHaveLength(0); + }); +}); + +// --------------------------------------------------------------------------- +// deleteFileBackedAttachment +// --------------------------------------------------------------------------- + +describe('deleteFileBackedAttachment', () => { + beforeEach(resetTables); + + test('deletes existing file-backed attachment', () => { + const created = createFileBackedAttachment({ + filename: 'recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 100, + filePath: '/data/recording.mp4', + }); + + const result = deleteFileBackedAttachment(created.id); + expect(result).toBe('deleted'); + + const fetched = getAttachmentById(created.id); + expect(fetched).toBeNull(); + }); + + test('returns not_found for nonexistent attachment', () => { + const result = deleteFileBackedAttachment('nonexistent-id'); + expect(result).toBe('not_found'); + }); +}); + +// --------------------------------------------------------------------------- +// Backward compatibility: existing base64 attachments still work +// --------------------------------------------------------------------------- + +describe('backward compatibility', () => { + beforeEach(resetTables); + + test('existing uploadAttachment still works correctly', () => { + const stored = uploadAttachment('chart.png', 'image/png', 'iVBORw0K'); + expect(stored.id).toBeDefined(); + expect(stored.storageKind).toBe('inline_base64'); + expect(stored.filePath).toBeNull(); + + const fetched = getAttachmentById(stored.id); + expect(fetched).not.toBeNull(); + expect(fetched!.dataBase64).toBe('iVBORw0K'); + expect(fetched!.storageKind).toBe('inline_base64'); + }); + + test('deduplication still works for inline base64', () => { + const first = uploadAttachment('a.png', 'image/png', 'DUPEDATA'); + const second = uploadAttachment('b.png', 'image/png', 'DUPEDATA'); + expect(first.id).toBe(second.id); + }); +}); diff --git a/assistant/src/__tests__/fixtures/media-reuse-fixtures.ts b/assistant/src/__tests__/fixtures/media-reuse-fixtures.ts index fa16c2c9717..44fb0ca0898 100644 --- a/assistant/src/__tests__/fixtures/media-reuse-fixtures.ts +++ b/assistant/src/__tests__/fixtures/media-reuse-fixtures.ts @@ -36,6 +36,10 @@ export const FAKE_SELFIE_ATTACHMENT: StoredAttachment = { sizeBytes: Buffer.from(TINY_PNG_BASE64, 'base64').length, kind: 'image', thumbnailBase64: null, + storageKind: 'inline_base64', + filePath: null, + sha256: null, + expiresAt: null, createdAt: NOW, }; @@ -47,6 +51,10 @@ export const FAKE_DOCUMENT_ATTACHMENT: StoredAttachment = { sizeBytes: 4096, kind: 'document', thumbnailBase64: null, + storageKind: 'inline_base64', + filePath: null, + sha256: null, + expiresAt: null, createdAt: NOW, }; @@ -58,6 +66,10 @@ export const FAKE_PHOTO_ATTACHMENT: StoredAttachment = { sizeBytes: Buffer.from(TINY_JPEG_BASE64, 'base64').length, kind: 'image', thumbnailBase64: null, + storageKind: 'inline_base64', + filePath: null, + sha256: null, + expiresAt: null, createdAt: NOW, }; diff --git a/assistant/src/__tests__/handlers-add-trust-rule-metadata.test.ts b/assistant/src/__tests__/handlers-add-trust-rule-metadata.test.ts index 88b585fba47..508f1522217 100644 --- a/assistant/src/__tests__/handlers-add-trust-rule-metadata.test.ts +++ b/assistant/src/__tests__/handlers-add-trust-rule-metadata.test.ts @@ -71,6 +71,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/handlers-cu-observation-blob.test.ts b/assistant/src/__tests__/handlers-cu-observation-blob.test.ts index 1f845194baa..aee0d11b651 100644 --- a/assistant/src/__tests__/handlers-cu-observation-blob.test.ts +++ b/assistant/src/__tests__/handlers-cu-observation-blob.test.ts @@ -84,6 +84,7 @@ function createTestContext(sessionId: string): { socketToSession: new Map(), cuSessions, socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/handlers-ipc-blob-probe.test.ts b/assistant/src/__tests__/handlers-ipc-blob-probe.test.ts index 13e44f0f870..006e990358f 100644 --- a/assistant/src/__tests__/handlers-ipc-blob-probe.test.ts +++ b/assistant/src/__tests__/handlers-ipc-blob-probe.test.ts @@ -61,6 +61,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/handlers-slack-config.test.ts b/assistant/src/__tests__/handlers-slack-config.test.ts index 1c597275026..2321808a8ec 100644 --- a/assistant/src/__tests__/handlers-slack-config.test.ts +++ b/assistant/src/__tests__/handlers-slack-config.test.ts @@ -91,6 +91,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/handlers-telegram-config.test.ts b/assistant/src/__tests__/handlers-telegram-config.test.ts index 495ac5e8914..4ba1d1bf752 100644 --- a/assistant/src/__tests__/handlers-telegram-config.test.ts +++ b/assistant/src/__tests__/handlers-telegram-config.test.ts @@ -138,6 +138,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/handlers-twilio-config.test.ts b/assistant/src/__tests__/handlers-twilio-config.test.ts index 01344d2b407..3b15d8f5741 100644 --- a/assistant/src/__tests__/handlers-twilio-config.test.ts +++ b/assistant/src/__tests__/handlers-twilio-config.test.ts @@ -152,6 +152,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/handlers-twitter-config.test.ts b/assistant/src/__tests__/handlers-twitter-config.test.ts index 1a58a50a59e..6a1b4e05f05 100644 --- a/assistant/src/__tests__/handlers-twitter-config.test.ts +++ b/assistant/src/__tests__/handlers-twitter-config.test.ts @@ -124,6 +124,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/ingress-reconcile.test.ts b/assistant/src/__tests__/ingress-reconcile.test.ts index 0532b195001..8332db2ff90 100644 --- a/assistant/src/__tests__/ingress-reconcile.test.ts +++ b/assistant/src/__tests__/ingress-reconcile.test.ts @@ -89,6 +89,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/ipc-snapshot.test.ts b/assistant/src/__tests__/ipc-snapshot.test.ts index 19c5da2edc9..da8978d8dc1 100644 --- a/assistant/src/__tests__/ipc-snapshot.test.ts +++ b/assistant/src/__tests__/ipc-snapshot.test.ts @@ -104,6 +104,23 @@ const clientMessages: Record = { type: 'cu_session_abort', sessionId: 'cu-sess-001', }, + cu_auto_approve_update: { + type: 'cu_auto_approve_update', + sessionId: 'cu-sess-001', + enabled: true, + }, + cu_recording_status: { + type: 'cu_recording_status', + sessionId: 'cu-sess-001', + status: 'started', + }, + cu_session_finalized: { + type: 'cu_session_finalized', + sessionId: 'cu-sess-001', + status: 'completed', + summary: 'Task completed successfully', + stepCount: 5, + }, cu_observation: { type: 'cu_observation', sessionId: 'cu-sess-001', @@ -145,6 +162,7 @@ const clientMessages: Record = { task: 'Open Safari and search for weather', screenWidth: 1920, screenHeight: 1080, + conversationId: 'conv-001', }, ui_surface_action: { type: 'ui_surface_action', @@ -395,8 +413,7 @@ const clientMessages: Record = { channel_readiness: { type: 'channel_readiness', action: 'get', - channel: 'sms', - includeRemote: true, + channel: 'telegram', }, guardian_verification: { type: 'guardian_verification', @@ -897,7 +914,7 @@ const serverMessages: Record = { ride_shotgun_progress: { type: 'ride_shotgun_progress', watchId: 'watch-shotgun-001', - message: 'Observing user activity...', + message: 'Analyzing screen content...', }, ride_shotgun_result: { type: 'ride_shotgun_result', @@ -1302,39 +1319,18 @@ const serverMessages: Record = { success: true, hasCredentials: true, phoneNumber: '+15551234567', - compliance: { - numberType: 'toll_free', - verificationSid: 'TF_VER_001', - verificationStatus: 'TWILIO_APPROVED', - }, - testResult: { - messageSid: 'SM-test-001', - to: '+15559876543', - initialStatus: 'queued', - finalStatus: 'delivered', - }, - diagnostics: { - readiness: { ready: true, issues: [] }, - compliance: { status: 'TWILIO_APPROVED', detail: 'Toll-free verification: TWILIO_APPROVED' }, - overallStatus: 'healthy', - actionItems: [], - }, }, channel_readiness_response: { type: 'channel_readiness_response', success: true, snapshots: [ { - channel: 'sms', - ready: false, - checkedAt: 1700000000000, + channel: 'telegram', + ready: true, + checkedAt: 1700000000, stale: false, - reasons: [{ code: 'twilio_credentials', text: 'Twilio credentials are not configured' }], - localChecks: [ - { name: 'twilio_credentials', passed: false, message: 'Twilio credentials are not configured' }, - { name: 'phone_number', passed: true, message: 'Phone number is assigned' }, - { name: 'ingress', passed: true, message: 'Public ingress URL is configured' }, - ], + reasons: [], + localChecks: [{ name: 'bot_token', passed: true, message: 'Bot token configured' }], }, ], }, diff --git a/assistant/src/__tests__/qa-intent.test.ts b/assistant/src/__tests__/qa-intent.test.ts new file mode 100644 index 00000000000..4bdaafe9186 --- /dev/null +++ b/assistant/src/__tests__/qa-intent.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, test } from 'bun:test'; +import { detectQaIntent, shouldRouteQaToComputerUse } from '../daemon/qa-intent.js'; + +describe('detectQaIntent', () => { + test('matches natural language QA phrasing', () => { + expect(detectQaIntent('Hey assistant, can you help me test this behavior')).toBe(true); + expect(detectQaIntent('I want to QA the vellum desktop app')).toBe(true); + }); + + test('does not match unrelated check requests', () => { + expect(detectQaIntent('Can you check my email?')).toBe(false); + }); +}); + +describe('shouldRouteQaToComputerUse', () => { + test('routes explicit UI/app QA requests to computer use', () => { + expect( + shouldRouteQaToComputerUse( + 'I want to QA the vellum desktop app and test out when a user types 2 lines in the composer', + ), + ).toBe(true); + expect(shouldRouteQaToComputerUse('Please test this workflow by clicking Send in the app')).toBe(true); + }); + + test('does not force computer use for code-test requests', () => { + expect(shouldRouteQaToComputerUse('Please write integration tests for this API handler')).toBe(false); + expect(shouldRouteQaToComputerUse('Can you add unit tests using vitest for this util')).toBe(false); + }); +}); diff --git a/assistant/src/__tests__/qa-latch-user-message.test.ts b/assistant/src/__tests__/qa-latch-user-message.test.ts new file mode 100644 index 00000000000..0c500ea5712 --- /dev/null +++ b/assistant/src/__tests__/qa-latch-user-message.test.ts @@ -0,0 +1,191 @@ +import { describe, expect, test, beforeEach } from 'bun:test'; +import { detectQaIntent, detectQaOptOut } from '../daemon/qa-intent.js'; +import { + setQaLatch, + clearQaLatch, + isQaLatchActive, + qaLatchByConversation, +} from '../daemon/handlers/shared.js'; + +/** + * Tests that the QA latch is correctly set/cleared based on user message + * content, mirroring the logic in handleUserMessage in sessions.ts. + * + * The handler defers latch updates until after the message has been accepted + * (past secret-ingress blocking and queue-rejection checks). We test the + * detection + latch functions directly rather than going through the full + * IPC handler, since the handler simply calls these functions on the message + * content after acceptance. This avoids the heavy Session/AgentLoop mock + * setup while still verifying the integration contract. + */ +describe('QA latch via user_message path', () => { + const sessionId = 'test-session-1'; + + beforeEach(() => { + // Clear all latch state between tests + qaLatchByConversation.clear(); + }); + + test('user message with QA intent sets the QA latch', () => { + const content = 'help me test Slack typing'; + expect(detectQaIntent(content)).toBe(true); + expect(detectQaOptOut(content)).toBe(false); + + // Simulate what handleUserMessage does: + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + + expect(isQaLatchActive(sessionId)).toBe(true); + }); + + test('user message with opt-out clears the QA latch', () => { + // Pre-set the latch + setQaLatch(sessionId); + expect(isQaLatchActive(sessionId)).toBe(true); + + const content = 'stop qa mode'; + expect(detectQaOptOut(content)).toBe(true); + + // Simulate what handleUserMessage does: + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + + expect(isQaLatchActive(sessionId)).toBe(false); + }); + + test('user message without QA intent does not change the latch', () => { + expect(isQaLatchActive(sessionId)).toBe(false); + + const content = 'Can you check my email?'; + expect(detectQaIntent(content)).toBe(false); + expect(detectQaOptOut(content)).toBe(false); + + // Simulate what handleUserMessage does: + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + + // Latch should remain unset + expect(isQaLatchActive(sessionId)).toBe(false); + }); + + test('opt-out takes priority over QA intent in the same message', () => { + // The current implementation checks opt-out first, so a message that + // somehow matches both patterns should clear rather than set. + setQaLatch(sessionId); + + // "stop testing" matches both detectQaOptOut (/\bstop\s+testing\b/) + // and could theoretically trigger QA patterns. The handler checks + // opt-out first, so the latch should be cleared. + const content = 'stop testing'; + const isOptOut = detectQaOptOut(content); + const isQa = detectQaIntent(content); + + // Simulate what handleUserMessage does (opt-out checked first): + if (isOptOut) { + clearQaLatch(sessionId); + } else if (isQa) { + setQaLatch(sessionId); + } + + expect(isQaLatchActive(sessionId)).toBe(false); + }); + + test('latch persists across multiple non-QA messages', () => { + // Set latch with QA intent + setQaLatch(sessionId); + expect(isQaLatchActive(sessionId)).toBe(true); + + // Subsequent non-QA messages should not affect the latch + const normalMessages = [ + 'What is the weather today?', + 'Send a message to the team', + 'Open the settings page', + ]; + + for (const content of normalMessages) { + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + } + + // Latch should still be active + expect(isQaLatchActive(sessionId)).toBe(true); + }); + + test('latch is not mutated when message would be rejected (contract test)', () => { + // This test documents the invariant enforced by handleUserMessage: + // QA detection only runs AFTER the message passes secret-ingress and + // queue-rejection checks. A rejected message must not flip the latch. + // + // We simulate by skipping the detection block entirely (as the handler + // does when it returns early on rejection). + expect(isQaLatchActive(sessionId)).toBe(false); + + const content = 'help me test Slack typing'; + expect(detectQaIntent(content)).toBe(true); + + // Simulate rejection: handler returns before reaching QA detection. + const messageRejected = true; + if (!messageRejected) { + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + } + + // Latch must remain unset because the message was rejected + expect(isQaLatchActive(sessionId)).toBe(false); + }); + + test('latch is not mutated when message is blocked by secret ingress (contract test)', () => { + // Similar to the rejection test: if checkIngressForSecrets blocks the + // message, the handler returns early and QA detection never runs. + setQaLatch(sessionId); + expect(isQaLatchActive(sessionId)).toBe(true); + + const content = 'stop qa mode'; + expect(detectQaOptOut(content)).toBe(true); + + // Simulate secret-ingress block: handler returns before QA detection. + const blockedBySecretIngress = true; + if (!blockedBySecretIngress) { + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + } + + // Latch must remain active because the opt-out message was blocked + expect(isQaLatchActive(sessionId)).toBe(true); + }); + + test('empty message content does not affect the latch', () => { + setQaLatch(sessionId); + expect(isQaLatchActive(sessionId)).toBe(true); + + const content = ''; + // In handleUserMessage, empty content skips QA detection entirely + if (content) { + if (detectQaOptOut(content)) { + clearQaLatch(sessionId); + } else if (detectQaIntent(content)) { + setQaLatch(sessionId); + } + } + + expect(isQaLatchActive(sessionId)).toBe(true); + }); +}); diff --git a/assistant/src/__tests__/recording-cleanup.test.ts b/assistant/src/__tests__/recording-cleanup.test.ts new file mode 100644 index 00000000000..9ee5b92ed0c --- /dev/null +++ b/assistant/src/__tests__/recording-cleanup.test.ts @@ -0,0 +1,237 @@ +import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test'; +import { mkdtempSync, rmSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const testDir = mkdtempSync(join(tmpdir(), 'recording-cleanup-test-')); + +mock.module('../util/platform.js', () => ({ + getDataDir: () => testDir, + isMacOS: () => process.platform === 'darwin', + isLinux: () => process.platform === 'linux', + isWindows: () => process.platform === 'win32', + getSocketPath: () => join(testDir, 'test.sock'), + getPidPath: () => join(testDir, 'test.pid'), + getDbPath: () => join(testDir, 'test.db'), + getLogPath: () => join(testDir, 'test.log'), + ensureDataDir: () => {}, + getRootDir: () => testDir, +})); + +mock.module('../util/logger.js', () => ({ + getLogger: () => new Proxy({} as Record, { + get: () => () => {}, + }), +})); + +mock.module('../config/loader.js', () => ({ + getConfig: () => ({ + model: 'test', + provider: 'test', + apiKeys: {}, + memory: { enabled: false }, + rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 }, + }), +})); + +import { initializeDb, getDb, resetDb } from '../memory/db.js'; +import { + uploadAttachment, + createFileBackedAttachment, + getAttachmentById, + getExpiredFileAttachments, +} from '../memory/attachments-store.js'; +import { runCleanupPass } from '../daemon/recording-cleanup.js'; + +initializeDb(); + +afterAll(() => { + resetDb(); + try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ } +}); + +function resetTables() { + const db = getDb(); + db.run('DELETE FROM message_attachments'); + db.run('DELETE FROM attachments'); +} + +// --------------------------------------------------------------------------- +// Cleanup pass tests +// --------------------------------------------------------------------------- + +describe('runCleanupPass', () => { + beforeEach(resetTables); + + test('deletes expired file-backed attachments and their files', () => { + const now = Date.now(); + const recordingsDir = join(testDir, 'recordings'); + mkdirSync(recordingsDir, { recursive: true }); + + // Create a file on disk + const filePath = join(recordingsDir, 'expired-recording.mp4'); + writeFileSync(filePath, Buffer.alloc(1024, 0)); // 1 KB dummy file + + // Create expired file-backed attachment + const expired = createFileBackedAttachment({ + filename: 'expired-recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 1024, + filePath, + expiresAt: now - 10000, // expired 10 seconds ago + }); + + // Verify file exists before cleanup + expect(existsSync(filePath)).toBe(true); + + const result = runCleanupPass(); + + expect(result.cleaned).toBe(1); + expect(result.bytesFreed).toBe(1024); + + // File should be removed from disk + expect(existsSync(filePath)).toBe(false); + + // DB row should be removed + expect(getAttachmentById(expired.id)).toBeNull(); + }); + + test('does not touch non-expired file-backed attachments', () => { + const now = Date.now(); + const recordingsDir = join(testDir, 'recordings'); + mkdirSync(recordingsDir, { recursive: true }); + + const filePath = join(recordingsDir, 'fresh-recording.mp4'); + writeFileSync(filePath, Buffer.alloc(512, 0)); + + const fresh = createFileBackedAttachment({ + filename: 'fresh-recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 512, + filePath, + expiresAt: now + 86400000, // expires tomorrow + }); + + const result = runCleanupPass(); + + expect(result.cleaned).toBe(0); + expect(result.bytesFreed).toBe(0); + + // File should still exist + expect(existsSync(filePath)).toBe(true); + + // DB row should still exist + expect(getAttachmentById(fresh.id)).not.toBeNull(); + }); + + test('never touches inline_base64 attachments', () => { + // Create an inline base64 attachment + const inline = uploadAttachment('chart.png', 'image/png', 'iVBORw0K'); + + const result = runCleanupPass(); + + expect(result.cleaned).toBe(0); + + // Inline attachment should still exist + expect(getAttachmentById(inline.id)).not.toBeNull(); + }); + + test('handles missing files gracefully (file already deleted)', () => { + const now = Date.now(); + + // Create expired attachment pointing to a non-existent file + const expired = createFileBackedAttachment({ + filename: 'ghost-recording.mp4', + mimeType: 'video/mp4', + sizeBytes: 2048, + filePath: join(testDir, 'nonexistent', 'ghost.mp4'), + expiresAt: now - 10000, + }); + + const result = runCleanupPass(); + + // Should still clean up the DB row even if the file is missing + expect(result.cleaned).toBe(1); + expect(result.bytesFreed).toBe(0); // no file to measure + + expect(getAttachmentById(expired.id)).toBeNull(); + }); + + test('cleans up multiple expired recordings in one pass', () => { + const now = Date.now(); + const recordingsDir = join(testDir, 'recordings-multi'); + mkdirSync(recordingsDir, { recursive: true }); + + const fileA = join(recordingsDir, 'a.mp4'); + const fileB = join(recordingsDir, 'b.mp4'); + writeFileSync(fileA, Buffer.alloc(2048, 0)); + writeFileSync(fileB, Buffer.alloc(4096, 0)); + + createFileBackedAttachment({ + filename: 'a.mp4', + mimeType: 'video/mp4', + sizeBytes: 2048, + filePath: fileA, + expiresAt: now - 5000, + }); + + createFileBackedAttachment({ + filename: 'b.mp4', + mimeType: 'video/mp4', + sizeBytes: 4096, + filePath: fileB, + expiresAt: now - 3000, + }); + + // Also add a non-expired one + const fileC = join(recordingsDir, 'c.mp4'); + writeFileSync(fileC, Buffer.alloc(1024, 0)); + createFileBackedAttachment({ + filename: 'c.mp4', + mimeType: 'video/mp4', + sizeBytes: 1024, + filePath: fileC, + expiresAt: now + 86400000, + }); + + const result = runCleanupPass(); + + expect(result.cleaned).toBe(2); + expect(result.bytesFreed).toBe(2048 + 4096); + + // Expired files gone + expect(existsSync(fileA)).toBe(false); + expect(existsSync(fileB)).toBe(false); + + // Non-expired file still present + expect(existsSync(fileC)).toBe(true); + }); + + test('returns zeros when no expired attachments exist', () => { + const result = runCleanupPass(); + expect(result.cleaned).toBe(0); + expect(result.bytesFreed).toBe(0); + }); + + test('file-backed attachments without expiresAt are never cleaned', () => { + const recordingsDir = join(testDir, 'recordings-no-expiry'); + mkdirSync(recordingsDir, { recursive: true }); + + const filePath = join(recordingsDir, 'permanent.mp4'); + writeFileSync(filePath, Buffer.alloc(256, 0)); + + const permanent = createFileBackedAttachment({ + filename: 'permanent.mp4', + mimeType: 'video/mp4', + sizeBytes: 256, + filePath, + // No expiresAt — should never be cleaned + }); + + const result = runCleanupPass(); + + expect(result.cleaned).toBe(0); + expect(existsSync(filePath)).toBe(true); + expect(getAttachmentById(permanent.id)).not.toBeNull(); + }); +}); diff --git a/assistant/src/__tests__/recording-intent.test.ts b/assistant/src/__tests__/recording-intent.test.ts new file mode 100644 index 00000000000..a7cdf43bea3 --- /dev/null +++ b/assistant/src/__tests__/recording-intent.test.ts @@ -0,0 +1,140 @@ +import { describe, test, expect } from 'bun:test'; +import { detectRecordingIntent } from '../daemon/recording-intent.js'; +import { detectQaIntent } from '../daemon/qa-intent.js'; + +describe('detectRecordingIntent', () => { + // ── Positive cases ────────────────────────────────────────────────────── + const positives = [ + 'record my screen', + 'record the screen', + 'record screen', + 'Record my display', + 'record my desktop', + 'record my session', + 'screen record this', + 'screenrecord while I work', + 'capture my screen', + 'capture the display', + 'capture my desktop', + 'record this', + 'record while I work', + 'record what I do', + 'record me doing this', + 'start recording', + 'record a video', + 'record video of my workflow', + 'video record this session', + 'make a recording', + 'take a recording', + 'take a screen recording', + ]; + + for (const input of positives) { + test(`detects: "${input}"`, () => { + expect(detectRecordingIntent(input)).toBe(true); + }); + } + + // ── Negative cases ────────────────────────────────────────────────────── + const negatives = [ + 'open Safari', + 'check my email', + 'write a function', + 'what is the weather', + 'record in the database', // "record" without screen/display/etc. + 'help me with this task', + 'open the recording app', // "recording" as noun, not a request + ]; + + for (const input of negatives) { + test(`does not detect: "${input}"`, () => { + expect(detectRecordingIntent(input)).toBe(false); + }); + } + + // ── Mixed QA + recording ──────────────────────────────────────────────── + // These prompts contain both QA intent AND recording intent. + // Both detectors should fire independently. + describe('mixed QA + recording prompts', () => { + const mixedPrompts = [ + 'test this behavior and record the screen', + 'QA the login flow and record my screen', + 'verify the signup and start recording', + 'test the app — record video of the session', + ]; + + for (const input of mixedPrompts) { + test(`"${input}" triggers both QA and recording intent`, () => { + expect(detectQaIntent(input)).toBe(true); + expect(detectRecordingIntent(input)).toBe(true); + }); + } + }); +}); + +// ── Routing integration tests ───────────────────────────────────────────── +// These verify the requiresRecording computation logic from misc.ts +// without needing to spin up the full handler. +describe('requiresRecording computation', () => { + // Mirrors the logic in handleTaskSubmit: + // const requiresRecording = msg.requiresRecording + // ?? (isRecordingRequested || (effectiveQa && config.qaRecording.enforceStartBeforeActions)); + function computeRequiresRecording(opts: { + msgOverride?: boolean; + isRecordingRequested: boolean; + effectiveQa: boolean; + enforceStartBeforeActions: boolean; + }): boolean { + return opts.msgOverride + ?? (opts.isRecordingRequested || (opts.effectiveQa && opts.enforceStartBeforeActions)); + } + + test('standalone recording request → requiresRecording = true', () => { + expect(computeRequiresRecording({ + isRecordingRequested: true, + effectiveQa: false, + enforceStartBeforeActions: false, + })).toBe(true); + }); + + test('QA intent + enforceStartBeforeActions → requiresRecording = true', () => { + expect(computeRequiresRecording({ + isRecordingRequested: false, + effectiveQa: true, + enforceStartBeforeActions: true, + })).toBe(true); + }); + + test('QA intent without enforceStartBeforeActions → requiresRecording = false', () => { + expect(computeRequiresRecording({ + isRecordingRequested: false, + effectiveQa: true, + enforceStartBeforeActions: false, + })).toBe(false); + }); + + test('mixed QA + recording → requiresRecording = true regardless of config', () => { + expect(computeRequiresRecording({ + isRecordingRequested: true, + effectiveQa: true, + enforceStartBeforeActions: false, + })).toBe(true); + }); + + test('explicit msg.requiresRecording overrides computation', () => { + expect(computeRequiresRecording({ + msgOverride: false, + isRecordingRequested: true, + effectiveQa: true, + enforceStartBeforeActions: true, + })).toBe(false); + }); + + test('no intent, no QA, no override → requiresRecording = false', () => { + expect(computeRequiresRecording({ + isRecordingRequested: false, + effectiveQa: false, + enforceStartBeforeActions: true, + })).toBe(false); + }); +}); diff --git a/assistant/src/__tests__/target-app-hints.test.ts b/assistant/src/__tests__/target-app-hints.test.ts new file mode 100644 index 00000000000..66a1dba602d --- /dev/null +++ b/assistant/src/__tests__/target-app-hints.test.ts @@ -0,0 +1,321 @@ +import { describe, test, expect } from 'bun:test'; +import { resolveComputerUseTargetAppHint } from '../daemon/target-app-hints.js'; + +describe('resolveComputerUseTargetAppHint', () => { + // ── Vellum (our app) ────────────────────────────────────────────── + describe('Vellum', () => { + test('matches "Vellum app"', () => { + const result = resolveComputerUseTargetAppHint('open the Vellum app'); + expect(result).toEqual({ appName: 'Vellum Assistant', bundleId: 'com.vellum.vellum-assistant' }); + }); + + test('matches "Velly desktop app"', () => { + const result = resolveComputerUseTargetAppHint('use the Velly desktop app'); + expect(result).toEqual({ appName: 'Vellum Assistant', bundleId: 'com.vellum.vellum-assistant' }); + }); + + test('matches "Vellum assistant"', () => { + const result = resolveComputerUseTargetAppHint('test the Vellum assistant'); + expect(result).toEqual({ appName: 'Vellum Assistant', bundleId: 'com.vellum.vellum-assistant' }); + }); + }); + + // ── Browsers ─────────────────────────────────────────────────────── + describe('Browsers', () => { + test('matches "chrome"', () => { + const result = resolveComputerUseTargetAppHint('open chrome and navigate to google.com'); + expect(result).toEqual({ appName: 'Google Chrome', bundleId: 'com.google.Chrome' }); + }); + + test('matches "Google Chrome"', () => { + const result = resolveComputerUseTargetAppHint('use Google Chrome to test the site'); + expect(result).toEqual({ appName: 'Google Chrome', bundleId: 'com.google.Chrome' }); + }); + + test('matches "safari"', () => { + const result = resolveComputerUseTargetAppHint('test in Safari'); + expect(result).toEqual({ appName: 'Safari', bundleId: 'com.apple.Safari' }); + }); + + test('matches "firefox"', () => { + const result = resolveComputerUseTargetAppHint('open Firefox'); + expect(result).toEqual({ appName: 'Firefox', bundleId: 'org.mozilla.firefox' }); + }); + + test('matches "arc browser"', () => { + const result = resolveComputerUseTargetAppHint('switch to Arc browser'); + expect(result).toEqual({ appName: 'Arc', bundleId: 'company.thebrowser.Browser' }); + }); + }); + + // ── Communication ────────────────────────────────────────────────── + describe('Communication', () => { + test('matches "slack"', () => { + const result = resolveComputerUseTargetAppHint('test slack typing'); + expect(result).toEqual({ appName: 'Slack', bundleId: 'com.tinyspeck.slackmacgap' }); + }); + + test('matches "discord"', () => { + const result = resolveComputerUseTargetAppHint('open discord and join the call'); + expect(result).toEqual({ appName: 'Discord', bundleId: 'com.hnc.Discord' }); + }); + + test('matches "open zoom"', () => { + const result = resolveComputerUseTargetAppHint('open zoom and join the call'); + expect(result).toEqual({ appName: 'zoom.us', bundleId: 'us.zoom.xos' }); + }); + + test('matches "zoom app"', () => { + const result = resolveComputerUseTargetAppHint('check the zoom app'); + expect(result).toEqual({ appName: 'zoom.us', bundleId: 'us.zoom.xos' }); + }); + + test('matches "Microsoft Teams"', () => { + const result = resolveComputerUseTargetAppHint('message them on Microsoft Teams'); + expect(result).toEqual({ appName: 'Microsoft Teams', bundleId: 'com.microsoft.teams2' }); + }); + + test('matches "teams app"', () => { + const result = resolveComputerUseTargetAppHint('open the teams app'); + expect(result).toEqual({ appName: 'Microsoft Teams', bundleId: 'com.microsoft.teams2' }); + }); + }); + + // ── Terminals ────────────────────────────────────────────────────── + describe('Terminals', () => { + test('matches "warp"', () => { + const result = resolveComputerUseTargetAppHint('open warp and run the command'); + expect(result).toEqual({ appName: 'Warp', bundleId: 'dev.warp.Warp-Stable' }); + }); + + test('matches "open Terminal"', () => { + const result = resolveComputerUseTargetAppHint('open terminal and run ls'); + expect(result).toEqual({ appName: 'Terminal', bundleId: 'com.apple.Terminal' }); + }); + + test('matches "in Terminal"', () => { + const result = resolveComputerUseTargetAppHint('run the command in terminal'); + expect(result).toEqual({ appName: 'Terminal', bundleId: 'com.apple.Terminal' }); + }); + + test('matches "iterm"', () => { + const result = resolveComputerUseTargetAppHint('switch to iterm'); + expect(result).toEqual({ appName: 'iTerm', bundleId: 'com.googlecode.iterm2' }); + }); + + test('matches "iterm2"', () => { + const result = resolveComputerUseTargetAppHint('use iterm2 for this'); + expect(result).toEqual({ appName: 'iTerm', bundleId: 'com.googlecode.iterm2' }); + }); + + test('"open terminal in iterm2" resolves to iTerm', () => { + const result = resolveComputerUseTargetAppHint('open terminal in iterm2'); + expect(result).toEqual({ appName: 'iTerm', bundleId: 'com.googlecode.iterm2' }); + }); + }); + + // ── IDEs ─────────────────────────────────────────────────────────── + describe('IDEs', () => { + test('matches "VS Code"', () => { + const result = resolveComputerUseTargetAppHint('open VS Code'); + expect(result).toEqual({ appName: 'Visual Studio Code', bundleId: 'com.microsoft.VSCode' }); + }); + + test('matches "vscode"', () => { + const result = resolveComputerUseTargetAppHint('open vscode'); + expect(result).toEqual({ appName: 'Visual Studio Code', bundleId: 'com.microsoft.VSCode' }); + }); + + test('matches "Visual Studio Code"', () => { + const result = resolveComputerUseTargetAppHint('use Visual Studio Code'); + expect(result).toEqual({ appName: 'Visual Studio Code', bundleId: 'com.microsoft.VSCode' }); + }); + + test('matches "open cursor"', () => { + const result = resolveComputerUseTargetAppHint('open cursor and edit the file'); + expect(result).toEqual({ appName: 'Cursor', bundleId: 'com.todesktop.230313mzl4w4u92' }); + }); + + test('matches "cursor app"', () => { + const result = resolveComputerUseTargetAppHint('check the cursor app'); + expect(result).toEqual({ appName: 'Cursor', bundleId: 'com.todesktop.230313mzl4w4u92' }); + }); + + test('matches "xcode"', () => { + const result = resolveComputerUseTargetAppHint('build the project in xcode'); + expect(result).toEqual({ appName: 'Xcode', bundleId: 'com.apple.dt.Xcode' }); + }); + }); + + // ── Productivity ─────────────────────────────────────────────────── + describe('Productivity', () => { + test('matches "notion"', () => { + const result = resolveComputerUseTargetAppHint('update the page in Notion'); + expect(result).toEqual({ appName: 'Notion', bundleId: 'notion.id' }); + }); + + test('matches "figma"', () => { + const result = resolveComputerUseTargetAppHint('check the design in Figma'); + expect(result).toEqual({ appName: 'Figma', bundleId: 'com.figma.Desktop' }); + }); + + test('matches "finder"', () => { + const result = resolveComputerUseTargetAppHint('browse files in Finder'); + expect(result).toEqual({ appName: 'Finder', bundleId: 'com.apple.finder' }); + }); + }); + + // ── Apple apps (context-required) ────────────────────────────────── + describe('Apple apps (context-required)', () => { + test('"open Notes and write" returns Notes', () => { + const result = resolveComputerUseTargetAppHint('open Notes and write something'); + expect(result).toEqual({ appName: 'Notes', bundleId: 'com.apple.Notes' }); + }); + + test('"in Notes" returns Notes', () => { + const result = resolveComputerUseTargetAppHint('create a list in notes'); + expect(result).toEqual({ appName: 'Notes', bundleId: 'com.apple.Notes' }); + }); + + test('"test Notes" returns Notes', () => { + const result = resolveComputerUseTargetAppHint('test notes search feature'); + expect(result).toEqual({ appName: 'Notes', bundleId: 'com.apple.Notes' }); + }); + + test('"Notes app" returns Notes', () => { + const result = resolveComputerUseTargetAppHint('check the notes app'); + expect(result).toEqual({ appName: 'Notes', bundleId: 'com.apple.Notes' }); + }); + + test('"iMessage" returns Messages', () => { + const result = resolveComputerUseTargetAppHint('send a text via iMessage'); + expect(result).toEqual({ appName: 'Messages', bundleId: 'com.apple.MobileSMS' }); + }); + + test('"open Messages" returns Messages', () => { + const result = resolveComputerUseTargetAppHint('open messages and reply'); + expect(result).toEqual({ appName: 'Messages', bundleId: 'com.apple.MobileSMS' }); + }); + + test('"open Mail" returns Mail', () => { + const result = resolveComputerUseTargetAppHint('open mail and check inbox'); + expect(result).toEqual({ appName: 'Mail', bundleId: 'com.apple.mail' }); + }); + + test('"Mail app" returns Mail', () => { + const result = resolveComputerUseTargetAppHint('use the mail app'); + expect(result).toEqual({ appName: 'Mail', bundleId: 'com.apple.mail' }); + }); + + test('"System Settings" returns System Settings', () => { + const result = resolveComputerUseTargetAppHint('open system settings'); + expect(result).toEqual({ appName: 'System Settings', bundleId: 'com.apple.systempreferences' }); + }); + + test('"System Preferences" returns System Settings', () => { + const result = resolveComputerUseTargetAppHint('check system preferences'); + expect(result).toEqual({ appName: 'System Settings', bundleId: 'com.apple.systempreferences' }); + }); + + test('"check Settings" returns System Settings', () => { + const result = resolveComputerUseTargetAppHint('check settings for accessibility'); + expect(result).toEqual({ appName: 'System Settings', bundleId: 'com.apple.systempreferences' }); + }); + + test('"Settings app" returns System Settings', () => { + const result = resolveComputerUseTargetAppHint('open the settings app'); + expect(result).toEqual({ appName: 'System Settings', bundleId: 'com.apple.systempreferences' }); + }); + }); + + // ── False-positive prevention ────────────────────────────────────── + describe('false positives', () => { + test('"take notes about the meeting" does NOT return Notes', () => { + const result = resolveComputerUseTargetAppHint('take notes about the meeting'); + expect(result).toBeUndefined(); + }); + + test('"write notes for the class" does NOT return Notes', () => { + const result = resolveComputerUseTargetAppHint('write notes for the class'); + expect(result).toBeUndefined(); + }); + + test('"send mail to Bob" does NOT return Mail', () => { + const result = resolveComputerUseTargetAppHint('send mail to Bob'); + expect(result).toBeUndefined(); + }); + + test('"read the messages carefully" does NOT return Messages', () => { + const result = resolveComputerUseTargetAppHint('read the messages carefully'); + expect(result).toBeUndefined(); + }); + + test('"change the settings in the config file" does NOT return System Settings', () => { + const result = resolveComputerUseTargetAppHint('change the settings in the config file'); + expect(result).toBeUndefined(); + }); + + test('"terminal velocity" does NOT return Terminal', () => { + const result = resolveComputerUseTargetAppHint('terminal velocity of the object'); + expect(result).toBeUndefined(); + }); + + test('"move cursor to the submit button" does NOT return Cursor', () => { + const result = resolveComputerUseTargetAppHint('move cursor to the submit button'); + expect(result).toBeUndefined(); + }); + + test('"zoom in on the chart" does NOT return Zoom', () => { + const result = resolveComputerUseTargetAppHint('zoom in on the chart'); + expect(result).toBeUndefined(); + }); + + test('"login terminal" does NOT return Terminal (word boundary)', () => { + const result = resolveComputerUseTargetAppHint('login terminal'); + expect(result).toBeUndefined(); + }); + + test('"domain mail server" does NOT return Mail (word boundary)', () => { + const result = resolveComputerUseTargetAppHint('domain mail server'); + expect(result).toBeUndefined(); + }); + + test('empty string returns undefined', () => { + const result = resolveComputerUseTargetAppHint(''); + expect(result).toBeUndefined(); + }); + + test('generic text returns undefined', () => { + const result = resolveComputerUseTargetAppHint('do something for me please'); + expect(result).toBeUndefined(); + }); + }); + + // ── Contextual task patterns ─────────────────────────────────────── + describe('contextual task patterns', () => { + test('"test slack typing" returns Slack', () => { + const result = resolveComputerUseTargetAppHint('test slack typing'); + expect(result).toEqual({ appName: 'Slack', bundleId: 'com.tinyspeck.slackmacgap' }); + }); + + test('"QA the discord voice chat" returns Discord', () => { + const result = resolveComputerUseTargetAppHint('QA the discord voice chat'); + expect(result).toEqual({ appName: 'Discord', bundleId: 'com.hnc.Discord' }); + }); + + test('"check chrome rendering" returns Chrome', () => { + const result = resolveComputerUseTargetAppHint('check chrome rendering'); + expect(result).toEqual({ appName: 'Google Chrome', bundleId: 'com.google.Chrome' }); + }); + + test('"launch terminal and run tests" returns Terminal', () => { + const result = resolveComputerUseTargetAppHint('launch terminal and run tests'); + expect(result).toEqual({ appName: 'Terminal', bundleId: 'com.apple.Terminal' }); + }); + + test('"use the terminal app to debug" returns Terminal', () => { + const result = resolveComputerUseTargetAppHint('use the terminal app to debug'); + expect(result).toEqual({ appName: 'Terminal', bundleId: 'com.apple.Terminal' }); + }); + }); +}); diff --git a/assistant/src/__tests__/tool-permission-simulate-handler.test.ts b/assistant/src/__tests__/tool-permission-simulate-handler.test.ts index 9083f4dc8fa..f288e3f3d3c 100644 --- a/assistant/src/__tests__/tool-permission-simulate-handler.test.ts +++ b/assistant/src/__tests__/tool-permission-simulate-handler.test.ts @@ -69,6 +69,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/__tests__/twitter-auth-handler.test.ts b/assistant/src/__tests__/twitter-auth-handler.test.ts index 21da7347a35..006e65e843c 100644 --- a/assistant/src/__tests__/twitter-auth-handler.test.ts +++ b/assistant/src/__tests__/twitter-auth-handler.test.ts @@ -160,6 +160,7 @@ function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } { socketToSession: new Map(), cuSessions: new Map(), socketToCuSession: new Map(), + cuSessionMetadata: new Map(), cuObservationParseSequence: new Map(), socketSandboxOverride: new Map(), sharedRequestTimestamps: [], diff --git a/assistant/src/config/bundled-skills/computer-use/TOOLS.json b/assistant/src/config/bundled-skills/computer-use/TOOLS.json index 98b9dc22d4d..ec2885fc37b 100644 --- a/assistant/src/config/bundled-skills/computer-use/TOOLS.json +++ b/assistant/src/config/bundled-skills/computer-use/TOOLS.json @@ -250,6 +250,10 @@ "type": "string", "description": "The name of the application to open (e.g. \"Slack\", \"Safari\", \"Google Chrome\", \"VS Code\")" }, + "app_bundle_id": { + "type": "string", + "description": "Bundle identifier of the app (e.g. com.apple.Safari). If provided, used for precise app activation." + }, "reasoning": { "type": "string", "description": "Explanation of why you need to open or switch to this app" diff --git a/assistant/src/config/bundled-skills/qa-testing/SKILL.md b/assistant/src/config/bundled-skills/qa-testing/SKILL.md new file mode 100644 index 00000000000..4b338416c56 --- /dev/null +++ b/assistant/src/config/bundled-skills/qa-testing/SKILL.md @@ -0,0 +1,93 @@ +--- +name: "QA Testing" +description: "Run QA and testing workflows with automatic screen recording, strict focus management, and post-session video analysis." +user-invocable: false +disable-model-invocation: false +includes: ["screen-recording", "media-processing"] +metadata: {"vellum": {"emoji": "🧪", "os": ["darwin"]}} +--- + +# QA Testing + +This skill orchestrates QA and testing workflows by composing screen recording and media analysis capabilities. + +## How QA Mode Works + +QA mode is activated automatically when the user's message indicates a testing intent — phrases like "test the login flow", "QA the checkout", "verify the form works", etc. You do NOT need to explicitly activate it. + +### What happens in QA mode: + +1. **Screen recording starts automatically** before any destructive actions +2. **Strict focus management** ensures the target app stays frontmost throughout +3. **Recording gate** blocks clicks/typing until the first video frame is captured +4. **Post-action focus drift** is terminal — if the target app loses focus, the session fails immediately +5. **On completion**, the recording is attached to the chat as a playable video + +### QA Latch + +Once QA mode is activated for a conversation, it "latches" — subsequent tasks in the same thread inherit QA mode automatically. The user can opt out with phrases like "stop QA mode" or "disable recording." + +## Strict Visual QA + +When a target app is specified (bundle ID or app name), strict visual QA activates: + +- **Pre-action**: Focus is verified before every destructive action +- **Post-action**: Focus is re-verified after every action +- **open_app verification**: After opening an app, FocusManager confirms it's actually frontmost +- **Failure is terminal**: Any focus drift immediately fails the session (no retry, no continue) + +This ensures the recording captures exactly what happened in the target app, with no accidental interactions in other apps. + +## Post-Session Analysis + +After a QA session completes, you can offer to analyze the recording: + +1. **Ingest** the recording using the media-processing skill's `ingest_media` tool +2. **Extract keyframes** to see what happened at each step +3. **Analyze keyframes** to detect UI changes, errors, or unexpected states +4. **Detect events** to find specific moments (button clicks, form submissions, errors) +5. **Generate clips** of interesting segments for sharing + +### Example follow-up offers: +- "Would you like me to analyze the recording to identify any issues?" +- "I can extract keyframes from the recording to create a visual summary of the test." +- "Want me to check if any error dialogs appeared during the test?" + +## Target App Scoping + +QA sessions are scoped to a specific app. The system: +- Injects the target app as a soft constraint in the system prompt +- Does NOT automatically open the app (you decide based on what's on screen) +- Blocks cross-app actions via the proxy resolver +- Uses FocusManager with multi-strategy activation (unhide, NSRunningApplication.activate, AX window raise) + +## Recording Lifecycle in QA + +``` +Session Start + -> ScreenRecorder.startRecording() + -> Wait for first frame (5s timeout) + -> If required and no frames: FAIL immediately + -> If optional and no frames: warn and continue + +During Session + -> Recording runs continuously + -> Focus verified before/after each action + +Session End + -> ScreenRecorder.stopRecording() -> RecordingResult + -> createFileBackedAttachment() stores metadata + -> linkAttachmentToMessage() ties video to chat + -> Video appears inline for playback + -> Cleanup worker deletes after retention period (default: 7 days) +``` + +## Error Handling + +When recording or focus issues occur, communicate specific errors to the user: +- "Screen Recording permission is not granted" — direct them to System Settings +- "First frame not received within 5 seconds" — capture pipeline issue +- "Target app lost focus during testing" — another app stole focus +- "Could not activate [app] after N attempts" — app may be unresponsive + +Do NOT use generic error messages. Always surface the specific failure reason. diff --git a/assistant/src/config/bundled-skills/screen-recording/SKILL.md b/assistant/src/config/bundled-skills/screen-recording/SKILL.md new file mode 100644 index 00000000000..aed4e7a7b24 --- /dev/null +++ b/assistant/src/config/bundled-skills/screen-recording/SKILL.md @@ -0,0 +1,62 @@ +--- +name: "Screen Recording" +description: "Capture screen recordings during computer-use sessions. Records the display or a specific window as H.264 MP4 video with optional audio." +user-invocable: false +disable-model-invocation: false +metadata: + vellum: + emoji: "🎥" + os: ["darwin"] +--- + +# Screen Recording + +You have access to a screen recording capability that captures what happens on the user's Mac during computer-use sessions. + +## How It Works + +- **Automatic in QA mode**: When a QA/test session starts, recording begins automatically before any destructive actions (clicks, typing, etc.) +- **Can be requested explicitly**: Sessions can be configured with `requiresRecording: true` to enable recording outside of QA mode +- **Recording gate**: When recording is required, destructive actions are blocked until the first video frame is confirmed captured + +## Recording Details + +- **Format**: H.264 MP4 video at 30 fps +- **Resolution**: 1920×1080 +- **Bitrate**: 4 Mbps video, 128 kbps AAC audio (when audio is enabled) +- **Capture scope**: Either the full display or a specific window +- **Storage**: Files are saved to `~/Library/Application Support/vellum-assistant/recordings/` +- **Naming**: `qa-recording-{timestamp}.mp4` + +## Health Checks + +A first-frame handshake verifies the capture pipeline is healthy within 5 seconds of starting. If no frames arrive: +- **Required recording**: The session fails immediately with a clear error +- **Optional recording**: A warning is shown but the session continues + +## After Recording + +When a recording completes: +1. The video file is saved to disk +2. A file-backed attachment is created (metadata in DB, file stays on disk) +3. The attachment is linked to the originating chat message +4. The video appears inline in the conversation for playback + +## Retention + +Recordings have an expiration timestamp (default: 7 days, configurable). An automatic cleanup worker runs periodically (every 6 hours) and deletes expired recording files from disk. + +## Limitations + +- Requires Screen Recording permission in System Settings > Privacy & Security +- Only available on macOS +- One recording at a time per session +- Recording must be stopped explicitly (or stops when the session ends) +- Large recordings consume disk space until cleanup runs + +## When to Mention Recording + +- Tell users their QA session is being recorded when relevant +- Offer to analyze recordings using the media-processing skill (keyframe extraction, event detection) +- Mention retention period if users ask about storage or cleanup +- If recording fails, explain the specific error (permission denied, no display found, etc.) diff --git a/assistant/src/config/computer-use-prompt.ts b/assistant/src/config/computer-use-prompt.ts index 2943b1fe10b..eeb9651e44c 100644 --- a/assistant/src/config/computer-use-prompt.ts +++ b/assistant/src/config/computer-use-prompt.ts @@ -18,12 +18,22 @@ function currentDateString(): string { export function buildComputerUseSystemPrompt( screenWidth: number, screenHeight: number, + targetAppName?: string, + targetAppBundleId?: string, ): string { const dateStr = currentDateString(); + const targetAppSection = targetAppName + ? ` +TARGET APP CONSTRAINT: +- This task is scoped to app "${targetAppName}"${targetAppBundleId ? ` (bundle id: ${targetAppBundleId})` : ''}. +- Do NOT interpret similarly named workspaces, channels, or documents in other apps as the target app. +- Do NOT switch to other apps unless the user explicitly requested a cross-app workflow.` + : ''; return `You are vellum-assistant's computer use agent. You control the user's Mac to accomplish tasks. The screen is ${screenWidth}\u00d7${screenHeight} pixels. +${targetAppSection} ACTION EXECUTION HIERARCHY: Not all actions require the same execution method. Always prefer the least invasive, most reliable approach. Foreground computer use (clicking, typing, scrolling) takes over the user's cursor and keyboard — it is disruptive and should be your LAST resort, not your first instinct. diff --git a/assistant/src/config/defaults.ts b/assistant/src/config/defaults.ts index fc9dcdb35fd..f666c1c06c3 100644 --- a/assistant/src/config/defaults.ts +++ b/assistant/src/config/defaults.ts @@ -257,6 +257,13 @@ export const DEFAULT_CONFIG: AssistantConfig = { codeLength: 6, }, }, + qaRecording: { + defaultRetentionDays: 7, + cleanupIntervalMs: 6 * 60 * 60 * 1000, // 6 hours + captureScope: 'display' as const, + includeAudio: false, + enforceStartBeforeActions: true, + }, sms: { enabled: false, provider: 'twilio' as const, diff --git a/assistant/src/config/schema.ts b/assistant/src/config/schema.ts index 1b70d5830af..4e13535f8e7 100644 --- a/assistant/src/config/schema.ts +++ b/assistant/src/config/schema.ts @@ -1080,6 +1080,29 @@ export const SkillsConfigSchema = z.object({ allowBundled: z.array(z.string()).nullable().default(null), }); +export const QaRecordingConfigSchema = z.object({ + defaultRetentionDays: z + .number({ error: 'qaRecording.defaultRetentionDays must be a number' }) + .int('qaRecording.defaultRetentionDays must be an integer') + .positive('qaRecording.defaultRetentionDays must be a positive integer') + .default(7), + cleanupIntervalMs: z + .number({ error: 'qaRecording.cleanupIntervalMs must be a number' }) + .int('qaRecording.cleanupIntervalMs must be an integer') + .positive('qaRecording.cleanupIntervalMs must be a positive integer') + .max(2_147_483_647, 'qaRecording.cleanupIntervalMs must be at most 2147483647 (setInterval-safe limit)') + .default(6 * 60 * 60 * 1000), + captureScope: z + .enum(['window', 'display'], { error: 'qaRecording.captureScope must be "window" or "display"' }) + .default('display'), + includeAudio: z + .boolean({ error: 'qaRecording.includeAudio must be a boolean' }) + .default(false), + enforceStartBeforeActions: z + .boolean({ error: 'qaRecording.enforceStartBeforeActions must be a boolean' }) + .default(true), +}); + export const SmsConfigSchema = z.object({ enabled: z .boolean({ error: 'sms.enabled must be a boolean' }) @@ -1398,6 +1421,13 @@ export const AssistantConfigSchema = z.object({ codeLength: 6, }, }), + qaRecording: QaRecordingConfigSchema.default({ + defaultRetentionDays: 7, + cleanupIntervalMs: 6 * 60 * 60 * 1000, + captureScope: 'display' as const, + includeAudio: false, + enforceStartBeforeActions: true, + }), sms: SmsConfigSchema.default({ enabled: false, provider: 'twilio', @@ -1467,6 +1497,7 @@ export type CallsSafetyConfig = z.infer; export type CallsVoiceConfig = z.infer; export type CallsElevenLabsConfig = z.infer; export type CallerIdentityConfig = z.infer; +export type QaRecordingConfig = z.infer; export type CallsVerificationConfig = z.infer; export type SmsConfig = z.infer; export type IngressConfig = z.infer; diff --git a/assistant/src/config/types.ts b/assistant/src/config/types.ts index 087de1c37c9..4cea1f98f92 100644 --- a/assistant/src/config/types.ts +++ b/assistant/src/config/types.ts @@ -37,6 +37,7 @@ export type { CallsVoiceConfig, CallsElevenLabsConfig, CallerIdentityConfig, + QaRecordingConfig, SmsConfig, IngressConfig, } from './schema.js'; diff --git a/assistant/src/daemon/computer-use-session.ts b/assistant/src/daemon/computer-use-session.ts index 49bcbe73083..09c1acde74d 100644 --- a/assistant/src/daemon/computer-use-session.ts +++ b/assistant/src/daemon/computer-use-session.ts @@ -29,6 +29,7 @@ const log = getLogger('computer-use-session'); const MAX_STEPS = 50; const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +const RECORDING_HANDSHAKE_TIMEOUT_MS = 8_000; // 8 seconds const MAX_HISTORY_ENTRIES = 10; const LOOP_DETECTION_WINDOW = 3; const CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD = 2; @@ -42,6 +43,20 @@ const AX_TREE_PLACEHOLDER = ''; type SessionState = 'idle' | 'awaiting_observation' | 'inferring' | 'complete' | 'error'; +/** + * Tool names considered destructive — these MUST NOT run before recording has + * started when `requiresRecording` is true. + */ +const DESTRUCTIVE_TOOLS = new Set([ + 'computer_use_click', + 'computer_use_double_click', + 'computer_use_right_click', + 'computer_use_type_text', + 'computer_use_key', + 'computer_use_scroll', + 'computer_use_drag', +]); + interface ActionRecord { step: number; toolName: string; @@ -60,6 +75,8 @@ export class ComputerUseSession { private readonly interactionType: 'computer_use' | 'text_qa'; private readonly onTerminal?: (sessionId: string) => void; private readonly preactivatedSkillIds: string[]; + private readonly targetAppName?: string; + private readonly targetAppBundleId?: string; private readonly skillProjectionState = new Map(); private readonly skillProjectionCache: SkillProjectionCache = {}; @@ -82,6 +99,24 @@ export class ComputerUseSession { private terminalNotified = false; private prompter: PermissionPrompter | null = null; + /** When true, low/medium-risk tool prompts are auto-approved without client round-trip. */ + autoApproveEnabled = false; + + /** Tracks client-side recording lifecycle. Set by cu_recording_status handler. */ + recordingGateStatus: 'pending' | 'started' | 'failed' | 'stopped' = 'pending'; + /** Failure reason from the last cu_recording_status(failed) message. */ + recordingFailureReason?: string; + /** When true, destructive actions are blocked until recording has started. */ + private readonly requiresRecording: boolean; + /** Whether this session is operating in QA mode. */ + private readonly qaMode: boolean; + /** When true, target app must be frontmost during interaction and recording must be valid. */ + private readonly strictVisualQa: boolean; + /** Set to true when an observation confirms the target app was frontmost. */ + hasTargetFocusEvidence = false; + /** Timer for the recording handshake timeout. */ + private recordingHandshakeTimer: ReturnType | null = null; + // Tracks the agent loop promise so callers can await session completion private loopPromise: Promise | null = null; @@ -95,6 +130,11 @@ export class ComputerUseSession { interactionType?: 'computer_use' | 'text_qa', onTerminal?: (sessionId: string) => void, preactivatedSkillIds?: string[], + targetAppName?: string, + targetAppBundleId?: string, + requiresRecording?: boolean, + qaMode?: boolean, + strictVisualQa?: boolean, ) { this.sessionId = sessionId; this.task = task; @@ -105,6 +145,23 @@ export class ComputerUseSession { this.interactionType = interactionType ?? 'computer_use'; this.onTerminal = onTerminal; this.preactivatedSkillIds = preactivatedSkillIds ?? ['computer-use']; + this.targetAppName = targetAppName; + this.targetAppBundleId = targetAppBundleId; + this.requiresRecording = requiresRecording ?? false; + this.qaMode = qaMode ?? false; + this.strictVisualQa = strictVisualQa ?? false; + + log.info( + { + sessionId, + qaMode: this.qaMode, + requiresRecording: this.requiresRecording, + targetAppName, + targetAppBundleId, + recordingGateStatus: this.recordingGateStatus, + }, + 'CU session initialized', + ); } // --------------------------------------------------------------------------- @@ -132,6 +189,17 @@ export class ComputerUseSession { this.previousAXTree = obs.axTree; } + // Track whether the target app was observed frontmost (for strict visual QA). + if (!this.hasTargetFocusEvidence && (obs.frontmostBundleId || obs.frontmostAppName)) { + const bundleMatch = this.targetAppBundleId && obs.frontmostBundleId + && obs.frontmostBundleId === this.targetAppBundleId; + const nameMatch = obs.frontmostAppName && this.isTargetAppMatch(obs.frontmostAppName); + if (bundleMatch || nameMatch) { + this.hasTargetFocusEvidence = true; + log.info({ sessionId: this.sessionId, frontmostAppName: obs.frontmostAppName, frontmostBundleId: obs.frontmostBundleId }, 'Target app focus evidence confirmed'); + } + } + if (this.state === 'awaiting_observation' && this.pendingObservation) { // Resolve the pending proxy tool result with updated screen context const content = this.buildObservationResultContent(obs, hadPreviousAXTree); @@ -155,6 +223,31 @@ export class ComputerUseSession { this.abort(); }, SESSION_TIMEOUT_MS); + // Recording handshake timeout: if requiresRecording is true and the + // recording gate is still pending after RECORDING_HANDSHAKE_TIMEOUT_MS, + // abort the session so it doesn't hang indefinitely. + if (this.requiresRecording && this.recordingGateStatus === 'pending') { + this.recordingHandshakeTimer = setTimeout(() => { + if (this.recordingGateStatus === 'pending') { + log.error( + { + sessionId: this.sessionId, + timeoutMs: RECORDING_HANDSHAKE_TIMEOUT_MS, + recordingGateStatus: this.recordingGateStatus, + requiresRecording: this.requiresRecording, + targetAppName: this.targetAppName, + targetAppBundleId: this.targetAppBundleId, + stepCount: this.stepCount, + }, + 'Recording handshake timeout — recording never started', + ); + this.abortWithError( + `Recording handshake timed out after ${RECORDING_HANDSHAKE_TIMEOUT_MS / 1000} seconds. The screen recording did not start. Session aborted because recording is required for QA sessions. Check screen recording permissions in System Settings > Privacy & Security.`, + ); + } + }, RECORDING_HANDSHAKE_TIMEOUT_MS); + } + const messages = this.buildMessages(obs, hadPreviousAXTree); this.loopPromise = this.runAgentLoop(messages).catch((err) => { // Catches errors from setup code (e.g. skill projection failures) that @@ -187,6 +280,10 @@ export class ComputerUseSession { clearTimeout(this.sessionTimer); this.sessionTimer = null; } + if (this.recordingHandshakeTimer) { + clearTimeout(this.recordingHandshakeTimer); + this.recordingHandshakeTimer = null; + } this.abortController?.abort(); // If waiting for an observation, resolve it as cancelled @@ -214,6 +311,44 @@ export class ComputerUseSession { this.notifyTerminal(); } + /** + * Abort the session with a specific error message. Used by the recording + * gate when a timeout or recording failure makes the session unrecoverable. + */ + private abortWithError(message: string): void { + if (this.state === 'complete' || this.state === 'error') return; + + log.error({ sessionId: this.sessionId }, message); + if (this.sessionTimer) { + clearTimeout(this.sessionTimer); + this.sessionTimer = null; + } + if (this.recordingHandshakeTimer) { + clearTimeout(this.recordingHandshakeTimer); + this.recordingHandshakeTimer = null; + } + this.abortController?.abort(); + + if (this.pendingObservation) { + this.pendingObservation.resolve({ content: message, isError: true }); + this.pendingObservation = null; + } + this.prompter?.dispose(); + for (const [, pending] of this.pendingSurfaceActions) { + pending.resolve({ content: message, isError: true }); + } + this.pendingSurfaceActions.clear(); + this.surfaceState.clear(); + + this.state = 'error'; + this.sendToClient({ + type: 'cu_error', + sessionId: this.sessionId, + message, + }); + this.notifyTerminal(); + } + isComplete(): boolean { return this.state === 'complete'; } @@ -222,6 +357,102 @@ export class ComputerUseSession { return this.state; } + private isTargetAppMatch(candidateAppName: string): boolean { + if (!this.targetAppName) return true; + const candidate = ComputerUseSession.normalizeAppLabel(candidateAppName); + const target = ComputerUseSession.normalizeAppLabel(this.targetAppName); + if (!candidate || !target) return true; + return candidate === target || target.includes(candidate) || candidate.includes(target); + } + + private extractAppleScriptActivationTarget(script: string): string | undefined { + // Match `tell application "Name" to activate` + const nameMatch = /tell\s+application\s+"([^"]+)"\s+to\s+activate/i.exec(script); + if (nameMatch) return nameMatch[1]; + // Match `tell application id "bundle.id" to activate` — return bundle ID + const idMatch = /tell\s+application\s+id\s+"([^"]+)"\s+to\s+activate/i.exec(script); + return idMatch?.[1]; + } + + private static normalizeAppLabel(value: string): string { + return value.toLowerCase().replace(/[^a-z0-9]/g, ''); + } + + /** + * Maps variant / long-form app names to a canonical short form so that + * e.g. "google chrome" and "chrome" are counted as the same app in + * {@link taskExplicitlyRequestsCrossApp}. Keys must be lowercase. + */ + private static readonly APP_CANONICAL_MAP: ReadonlyMap = new Map([ + ['google chrome', 'chrome'], + ['microsoft teams', 'teams'], + ['apple notes', 'notes'], + ['apple music', 'music'], + ['vellum assistant', 'vellum'], + ['visual studio code', 'vscode'], + ['iterm2', 'iterm'], + ['gmail', 'gmail'], + ]); + + /** + * Well-known app names used to detect cross-app intent in task text. + * Only needs to cover apps commonly referenced in cross-app workflows; + * the list does not need to be exhaustive. + */ + private static readonly KNOWN_APP_NAMES: ReadonlySet = new Set([ + 'chrome', 'google chrome', 'safari', 'firefox', 'arc', 'brave', 'edge', + 'slack', 'discord', 'zoom', 'teams', 'microsoft teams', + 'notion', 'obsidian', 'bear', 'notes', 'apple notes', + 'finder', 'terminal', 'iterm', 'iterm2', 'warp', + 'vscode', 'visual studio code', 'cursor', 'xcode', 'intellij', 'webstorm', + 'figma', 'sketch', 'photoshop', 'illustrator', + 'mail', 'outlook', 'gmail', 'thunderbird', + 'spotify', 'music', 'apple music', + 'messages', 'imessage', 'whatsapp', 'telegram', 'signal', + 'calendar', 'reminders', 'todoist', 'things', + 'pages', 'numbers', 'keynote', 'word', 'excel', 'powerpoint', + 'preview', 'acrobat', 'pdf expert', + 'vellum', 'vellum assistant', + 'linear', 'jira', 'github', 'gitlab', + 'postman', 'docker', 'tableplus', 'sequel pro', + 'system preferences', 'system settings', 'activity monitor', + ]); + + /** + * Returns true when the original user task text explicitly requests a + * cross-app workflow (e.g. "copy from Chrome and paste into Vellum"). + * Only the user's original task counts — model-generated reasoning + * about switching apps does NOT qualify as an escape. + * + * Detection strategy: check whether the task text mentions at least two + * different known app names. This avoids false positives from generic + * phrases like "switch to dark mode" or "move the file to trash". + */ + private taskExplicitlyRequestsCrossApp(): boolean { + if (!this.task) return false; + const t = this.task.toLowerCase(); + + // Collect distinct app names mentioned in the task text. + const mentionedApps = new Set(); + for (const appName of ComputerUseSession.KNOWN_APP_NAMES) { + // Word-boundary check: the app name must appear as a standalone word/phrase, + // not as a substring of another word. + const escaped = appName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if (new RegExp(`\\b${escaped}\\b`).test(t)) { + // Normalize to a canonical form so e.g. "google chrome" and "chrome" + // are counted as the same app. Check the canonical map first so + // variant names like "google chrome" collapse to the same key as + // "chrome" instead of normalizing to distinct strings. + const canonical = ComputerUseSession.APP_CANONICAL_MAP.get(appName) + ?? ComputerUseSession.normalizeAppLabel(appName); + mentionedApps.add(canonical); + } + // Early exit once we confirm at least two distinct apps. + if (mentionedApps.size >= 2) return true; + } + return false; + } + /** * Compute CU tool definitions from the bundled computer-use skill via * skill projection. Returns null if projection fails so the caller can @@ -278,7 +509,12 @@ export class ComputerUseSession { // --------------------------------------------------------------------------- private async runAgentLoop(messages: Message[]): Promise { - const systemPrompt = buildComputerUseSystemPrompt(this.screenWidth, this.screenHeight); + const systemPrompt = buildComputerUseSystemPrompt( + this.screenWidth, + this.screenHeight, + this.targetAppName, + this.targetAppBundleId, + ); let cuToolDefs = this.getProjectedCuToolDefinitions(); if (!cuToolDefs) { @@ -308,9 +544,37 @@ export class ComputerUseSession { ]; this.prompter = new PermissionPrompter(this.sendToClient); - const prompter = this.prompter; + const innerPrompter = this.prompter; const secretPrompter = new SecretPrompter(this.sendToClient); - const executor = new ToolExecutor(prompter); + + // Wrap the prompter so low/medium-risk tools are auto-approved when + // the client has toggled auto-approve on, avoiding the IPC round-trip. + const sessionRef = this; + const autoApprovePrompter = new Proxy(innerPrompter, { + get(target, prop, receiver) { + if (prop === 'prompt') { + return async function ( + toolName: string, + input: Record, + riskLevel: string, + ...rest: unknown[] + ) { + const normalizedRisk = riskLevel.toLowerCase(); + if (sessionRef.autoApproveEnabled && (normalizedRisk === 'low' || normalizedRisk === 'medium')) { + log.info( + { sessionId: sessionRef.sessionId, toolName, riskLevel: normalizedRisk }, + 'Auto-approved tool prompt (proactive, skipping client round-trip)', + ); + return { decision: 'allow' as const }; + } + return (target.prompt as Function).call(target, toolName, input, riskLevel, ...rest); + }; + } + return Reflect.get(target, prop, receiver); + }, + }) as PermissionPrompter; + + const executor = new ToolExecutor(autoApprovePrompter); const proxyResolver = async ( toolName: string, @@ -421,6 +685,114 @@ export class ComputerUseSession { }); } + // Fail-closed guard: when a target app is set, block ALL non-matching + // open_app and run_applescript activations. The only escape is when the + // user's original task text explicitly requests a cross-app workflow. + if (toolName === 'computer_use_open_app') { + const requestedApp = + (typeof input.app_name === 'string' ? input.app_name : undefined) + ?? (typeof input.appName === 'string' ? input.appName : undefined); + if ( + requestedApp + && !this.isTargetAppMatch(requestedApp) + && !this.taskExplicitlyRequestsCrossApp() + ) { + log.warn({ + sessionId: this.sessionId, + toolName, + requestedApp, + targetApp: this.targetAppName, + crossAppEscapeChecked: true, + }, 'Blocked non-target app activation'); + return { + content: `Blocked: this task is scoped to "${this.targetAppName}". Do not switch to "${requestedApp}". Only the user can authorize cross-app workflows.`, + isError: true, + }; + } + + // Inject targetAppBundleId only when the requested app matches the target app + if (!input.app_bundle_id && this.targetAppBundleId && (!requestedApp || this.isTargetAppMatch(requestedApp))) { + input = { ...input, app_bundle_id: this.targetAppBundleId }; + } + } + + if (toolName === 'computer_use_run_applescript') { + const script = typeof input.script === 'string' ? input.script : undefined; + const activatedApp = script ? this.extractAppleScriptActivationTarget(script) : undefined; + + // In strict visual QA mode, block ALL AppleScript except activation of the target app itself. + // AppleScript can silently interact with apps in the background, bypassing the visual requirement. + if (this.strictVisualQa) { + // Allow both `tell application "Name" to activate` and `tell application id "bundle.id" to activate` + const isActivationScript = script && ( + /^\s*tell\s+application\s+"[^"]+"\s+to\s+activate\s*$/i.test(script.trim()) + || /^\s*tell\s+application\s+id\s+"[^"]+"\s+to\s+activate\s*$/i.test(script.trim()) + ); + const isTargetByName = activatedApp && this.isTargetAppMatch(activatedApp); + const isTargetByBundleId = this.targetAppBundleId && script + && new RegExp(`tell\\s+application\\s+id\\s+"${this.targetAppBundleId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s+to\\s+activate`, 'i').test(script); + const isTargetActivation = isActivationScript && (isTargetByName || isTargetByBundleId); + if (!isTargetActivation) { + log.warn({ + sessionId: this.sessionId, + toolName, + strictVisualQa: true, + script: script?.slice(0, 200), + }, 'Blocked AppleScript in strict visual QA mode'); + return { + content: 'Blocked: strict visual QA mode requires all interactions to be visible on screen. AppleScript is not allowed except for activating the target app. Use mouse/keyboard actions instead.', + isError: true, + }; + } + } + + if ( + activatedApp + && !this.isTargetAppMatch(activatedApp) + && !this.taskExplicitlyRequestsCrossApp() + ) { + log.warn({ + sessionId: this.sessionId, + toolName, + requestedApp: activatedApp, + targetApp: this.targetAppName, + crossAppEscapeChecked: true, + }, 'Blocked non-target AppleScript activation'); + return { + content: `Blocked: this task is scoped to "${this.targetAppName}". AppleScript cannot activate "${activatedApp}". Only the user can authorize cross-app workflows.`, + isError: true, + }; + } + } + + // ── Recording gate: block destructive actions before recording ── + if (this.requiresRecording && this.recordingGateStatus !== 'started') { + if (this.recordingGateStatus === 'failed') { + const reason = this.recordingFailureReason ?? 'unknown'; + this.abortWithError(`Recording failed to start: ${reason}. Session aborted because recording is required for QA sessions.`); + return { + content: `Recording failed: ${reason}. Session aborted.`, + isError: true, + }; + } + if (this.recordingGateStatus === 'pending' && DESTRUCTIVE_TOOLS.has(toolName)) { + log.warn( + { + sessionId: this.sessionId, + toolName, + recordingGateStatus: this.recordingGateStatus, + requiresRecording: this.requiresRecording, + stepCount: this.stepCount, + }, + 'Blocked destructive action — recording not started', + ); + return { + content: 'Recording has not started yet. Waiting for recording confirmation before executing destructive actions. Use computer_use_wait to wait, or computer_use_done/computer_use_respond if the task can be completed without interaction.', + isError: true, + }; + } + } + // ── Computer-use tool proxying ───────────────────────────────── const reasoning = typeof input.reasoning === 'string' ? input.reasoning : undefined; @@ -434,6 +806,27 @@ export class ComputerUseSession { // Check for terminal tools if (toolName === 'computer_use_done' || toolName === 'computer_use_respond') { + // In strict visual QA mode, reject completion if we never observed the target app frontmost. + if (this.strictVisualQa && !this.hasTargetFocusEvidence) { + log.warn({ + sessionId: this.sessionId, + toolName, + targetAppName: this.targetAppName, + targetAppBundleId: this.targetAppBundleId, + }, 'Rejected completion in strict visual QA: target app was never observed frontmost'); + + this.state = 'error'; + const failReason = `Strict visual QA failed: target app "${this.targetAppName ?? this.targetAppBundleId}" was never observed as the frontmost application during the session.`; + this.sendToClient({ + type: 'cu_error', + sessionId: this.sessionId, + message: failReason, + }); + this.abortController?.abort(); + this.notifyTerminal(); + return { content: failReason, isError: true }; + } + const summary = toolName === 'computer_use_done' ? (typeof input.summary === 'string' ? input.summary : 'Task completed') @@ -603,6 +996,10 @@ export class ComputerUseSession { clearTimeout(this.sessionTimer); this.sessionTimer = null; } + if (this.recordingHandshakeTimer) { + clearTimeout(this.recordingHandshakeTimer); + this.recordingHandshakeTimer = null; + } } } @@ -747,6 +1144,17 @@ export class ComputerUseSession { // Text block const textParts: string[] = []; + if (this.targetAppName) { + textParts.push(`TARGET APP: ${this.targetAppName}`); + if (this.targetAppBundleId) { + textParts.push(`TARGET BUNDLE ID: ${this.targetAppBundleId}`); + } + textParts.push( + 'Constraint: Treat similarly named workspaces/documents in other apps as unrelated. Stay in the target app unless the user explicitly asks for a cross-app workflow.', + ); + textParts.push(''); + } + const trimmedTask = this.task.trim(); if (trimmedTask) { textParts.push(`TASK: ${trimmedTask}`); diff --git a/assistant/src/daemon/handlers/computer-use.ts b/assistant/src/daemon/handlers/computer-use.ts index 0567047da6e..ad4b8ffdb04 100644 --- a/assistant/src/daemon/handlers/computer-use.ts +++ b/assistant/src/daemon/handlers/computer-use.ts @@ -7,10 +7,15 @@ import { readBlob, deleteBlob, validateBlobKindEncoding } from '../ipc-blob-stor import type { CuSessionCreate, CuSessionAbort, + CuAutoApproveUpdate, + CuRecordingStatus, + CuSessionFinalized, CuObservation, ServerMessage, } from '../ipc-protocol.js'; -import { log, defineHandlers, type HandlerContext } from './shared.js'; +import * as conversationStore from '../../memory/conversation-store.js'; +import { createFileBackedAttachment, linkAttachmentToMessage } from '../../memory/attachments-store.js'; +import { log, defineHandlers, findSocketForSession, type HandlerContext, type CuSessionMetadata } from './shared.js'; const cuObservationSequenceBySession = new Map(); @@ -24,13 +29,20 @@ function removeCuSessionReferences( return; } ctx.cuSessions.delete(sessionId); + // NOTE: cuSessionMetadata is intentionally NOT deleted here. + // onTerminal fires before cu_session_finalized arrives from the client, + // so deleting metadata here would race with handleCuSessionFinalized + // which still needs to read it. Metadata is cleaned up explicitly at the + // end of handleCuSessionFinalized instead. cuObservationSequenceBySession.delete(sessionId); ctx.cuObservationParseSequence.delete(sessionId); - for (const [sock, ids] of ctx.socketToCuSession) { - if (ids.delete(sessionId) && ids.size === 0) { - ctx.socketToCuSession.delete(sock); - } - } + // NOTE: socketToCuSession is intentionally NOT cleaned up here. + // The socket close handler in server.ts is the sole owner of + // socketToCuSession cleanup — it uses the mapping to find and delete + // cuSessionMetadata entries. Removing the session here would race: + // if a CU session reaches terminal state (triggering this function) + // and then the socket disconnects before cu_session_finalized, + // the close handler wouldn't see the session and metadata would leak. } export function handleCuSessionCreate( @@ -45,6 +57,16 @@ export function handleCuSessionCreate( if (existingSession) { existingSession.abort(); removeCuSessionReferences(ctx, msg.sessionId, existingSession); + // Clean up stale metadata from the replaced session; the new session + // will set its own metadata below if needed. + ctx.cuSessionMetadata.delete(msg.sessionId); + // Remove the session ID from the old socket's set so the old socket's + // close handler won't abort the replacement session. + for (const [sock, ids] of ctx.socketToCuSession) { + if (ids.delete(msg.sessionId) && ids.size === 0) { + ctx.socketToCuSession.delete(sock); + } + } } const config = getConfig(); @@ -73,11 +95,26 @@ export function handleCuSessionCreate( sendToClient, msg.interactionType, onTerminal, + undefined, + msg.targetAppName, + msg.targetAppBundleId, + msg.requiresRecording, + msg.qaMode, + msg.strictVisualQa, ); sessionRef.current = session; ctx.cuSessions.set(msg.sessionId, session); + // Store QA metadata so handleCuSessionFinalized can inject results + // into the originating chat session. + if (msg.reportToSessionId || msg.qaMode) { + const meta: CuSessionMetadata = {}; + if (msg.reportToSessionId) meta.reportToSessionId = msg.reportToSessionId; + if (msg.qaMode) meta.qaMode = msg.qaMode; + ctx.cuSessionMetadata.set(msg.sessionId, meta); + } + // Track all CU sessions per socket so disconnect cleans up all of them let sessionIds = ctx.socketToCuSession.get(socket); if (!sessionIds) { @@ -101,6 +138,8 @@ export function handleCuSessionAbort( } session.abort(); removeCuSessionReferences(ctx, msg.sessionId, session); + // On explicit abort, clean up metadata too — no finalized event is guaranteed. + ctx.cuSessionMetadata.delete(msg.sessionId); log.info({ sessionId: msg.sessionId }, 'Computer-use session aborted by client'); } @@ -180,8 +219,199 @@ export async function handleCuObservation( }); } +export function handleCuSessionFinalized( + msg: CuSessionFinalized, + _socket: net.Socket, + ctx: HandlerContext, +): void { + const meta = ctx.cuSessionMetadata.get(msg.sessionId); + let recordingTracked = false; + + log.info( + { + sessionId: msg.sessionId, + status: msg.status, + stepCount: msg.stepCount, + hasRecording: !!msg.recording, + recordingSizeBytes: msg.recording?.sizeBytes, + recordingDurationMs: msg.recording?.durationMs, + reportToSessionId: meta?.reportToSessionId, + qaMode: meta?.qaMode, + }, + 'CU session finalized by client', + ); + + // Inject a summary message into the originating chat session if configured. + if (meta?.reportToSessionId && msg.summary) { + const reportSessionId = meta.reportToSessionId; + const reportSocket = findSocketForSession(reportSessionId, ctx); + + // Persist the assistant message in the conversation store so it appears + // in history even if the client is not currently connected. + const conversation = conversationStore.getConversation(reportSessionId); + if (conversation) { + const assistantContent = JSON.stringify([{ type: 'text', text: msg.summary }]); + const persistedMessage = conversationStore.addMessage(reportSessionId, 'assistant', assistantContent, { + source: 'cu_session_finalized', + cuSessionId: msg.sessionId, + cuStatus: msg.status, + cuStepCount: msg.stepCount, + qaMode: meta.qaMode ?? false, + ...(msg.recording ? { recordingPath: msg.recording.localPath } : {}), + }); + + // Create a file-backed attachment from the recording and link it to the message. + let recordingAttachment: { id: string; filename: string; mimeType: string; sizeBytes: number } | undefined; + if (msg.recording) { + try { + const attachment = createFileBackedAttachment({ + filename: `qa-recording-${msg.sessionId}.mp4`, + mimeType: msg.recording.mimeType || 'video/mp4', + sizeBytes: msg.recording.sizeBytes, + filePath: msg.recording.localPath, + sha256: undefined, + expiresAt: msg.recording.expiresAt, + }); + linkAttachmentToMessage(persistedMessage.id, attachment.id, 0); + recordingTracked = true; + recordingAttachment = { + id: attachment.id, + filename: attachment.originalFilename, + mimeType: attachment.mimeType, + sizeBytes: attachment.sizeBytes, + }; + log.info( + { + sessionId: msg.sessionId, + attachmentId: attachment.id, + filePath: msg.recording.localPath, + sizeBytes: msg.recording.sizeBytes, + }, + 'Created file-backed attachment for CU session recording', + ); + } catch (err) { + log.error( + { err, sessionId: msg.sessionId, localPath: msg.recording.localPath }, + 'Failed to create file-backed attachment for recording', + ); + } + } + + // Also append to the in-memory Session.messages so subsequent turns + // in the same session see the injected summary without a reload. + const activeSession = ctx.sessions.get(reportSessionId); + if (activeSession) { + activeSession.messages.push({ + role: 'assistant', + content: [{ type: 'text', text: msg.summary }], + }); + } + + // If the reporting session has a connected client, stream the summary + // so it appears in real time. + if (reportSocket) { + ctx.send(reportSocket, { + type: 'assistant_text_delta', + text: msg.summary, + sessionId: reportSessionId, + }); + ctx.send(reportSocket, { + type: 'message_complete', + sessionId: reportSessionId, + ...(recordingAttachment ? { + attachments: [{ + id: recordingAttachment.id, + filename: recordingAttachment.filename, + mimeType: recordingAttachment.mimeType, + data: '', + sizeBytes: recordingAttachment.sizeBytes, + }], + } : {}), + }); + } + + log.info( + { cuSessionId: msg.sessionId, reportToSessionId: reportSessionId }, + 'Injected CU finalization summary into reporting session', + ); + } else { + log.warn( + { cuSessionId: msg.sessionId, reportToSessionId: reportSessionId }, + 'Reporting session conversation not found; summary not persisted', + ); + } + } + + // Create a fallback file-backed attachment for any recording that wasn't + // already tracked (no reportToSessionId, missing conversation, empty summary, + // or attachment creation failure) so cleanup can track the file on disk. + if (msg.recording && !recordingTracked) { + try { + createFileBackedAttachment({ + filename: `qa-recording-${msg.sessionId}.mp4`, + mimeType: msg.recording.mimeType || 'video/mp4', + sizeBytes: msg.recording.sizeBytes, + filePath: msg.recording.localPath, + sha256: undefined, + expiresAt: msg.recording.expiresAt, + }); + log.info({ sessionId: msg.sessionId }, 'Created fallback file-backed attachment for cleanup tracking'); + } catch (err) { + log.error({ err, sessionId: msg.sessionId }, 'Failed to create file-backed attachment for orphan recording'); + } + } + + // Clean up all CU session state. + removeCuSessionReferences(ctx, msg.sessionId); + // Delete metadata last — after it has been consumed for summary injection + // above and after removeCuSessionReferences (which intentionally skips it). + ctx.cuSessionMetadata.delete(msg.sessionId); +} + +export function handleCuAutoApproveUpdate( + msg: CuAutoApproveUpdate, + _socket: net.Socket, + ctx: HandlerContext, +): void { + const session = ctx.cuSessions.get(msg.sessionId); + if (!session) { + log.debug({ sessionId: msg.sessionId }, 'CU auto-approve update: session not found (already finished?)'); + return; + } + session.autoApproveEnabled = msg.enabled; + log.info( + { sessionId: msg.sessionId, autoApproveEnabled: msg.enabled }, + 'CU session auto-approve state changed', + ); +} + +function handleCuRecordingStatus(msg: CuRecordingStatus, _socket: net.Socket, ctx: HandlerContext) { + const session = ctx.cuSessions.get(msg.sessionId); + if (!session) { + log.warn({ sessionId: msg.sessionId }, 'CU recording status for unknown session'); + return; + } + const previousStatus = session.recordingGateStatus; + session.recordingGateStatus = msg.status; + if (msg.status === 'failed' && msg.reason) { + session.recordingFailureReason = msg.reason; + } + log.info( + { + sessionId: msg.sessionId, + previousStatus, + newStatus: msg.status, + reason: msg.reason, + }, + 'Recording gate status changed', + ); +} + export const computerUseHandlers = defineHandlers({ cu_session_create: handleCuSessionCreate, cu_session_abort: handleCuSessionAbort, + cu_auto_approve_update: handleCuAutoApproveUpdate, + cu_recording_status: handleCuRecordingStatus, + cu_session_finalized: handleCuSessionFinalized, cu_observation: handleCuObservation, }); diff --git a/assistant/src/daemon/handlers/index.ts b/assistant/src/daemon/handlers/index.ts index 87004c3a374..e004f5b3a6c 100644 --- a/assistant/src/daemon/handlers/index.ts +++ b/assistant/src/daemon/handlers/index.ts @@ -27,6 +27,7 @@ import { dictationHandlers } from './dictation.js'; // Re-export types and utilities for backwards compatibility export type { HandlerContext, + CuSessionMetadata, SessionCreateOptions, HistoryToolCall, HistorySurface, diff --git a/assistant/src/daemon/handlers/misc.ts b/assistant/src/daemon/handlers/misc.ts index a086c80c17e..8a7b707907b 100644 --- a/assistant/src/daemon/handlers/misc.ts +++ b/assistant/src/daemon/handlers/misc.ts @@ -18,8 +18,11 @@ import type { IpcBlobProbe, CuSessionCreate, } from '../ipc-protocol.js'; -import { log, wireEscalationHandler, renderHistoryContent, defineHandlers, type HandlerContext } from './shared.js'; +import { log, wireEscalationHandler, renderHistoryContent, defineHandlers, setQaLatch, clearQaLatch, isQaLatchActive, type HandlerContext } from './shared.js'; import { handleCuSessionCreate } from './computer-use.js'; +import { detectQaIntent, detectQaOptOut, shouldRouteQaToComputerUse } from '../qa-intent.js'; +import { detectRecordingIntent } from '../recording-intent.js'; +import { resolveComputerUseTargetAppHint } from '../target-app-hints.js'; // ─── Task submit handler ──────────────────────────────────────────────────── @@ -66,14 +69,48 @@ export async function handleTaskSubmit( // Slash candidates always route to text_qa — bypass classifier const slashCandidate = parseSlashCandidate(msg.task); + const isQa = detectQaIntent(msg.task); + const isOptOut = detectQaOptOut(msg.task); + const qaLatchActive = isQaLatchActive(msg.conversationId); + const forceQaComputerUse = shouldRouteQaToComputerUse(msg.task); + const isRecordingRequested = detectRecordingIntent(msg.task); const interactionType = slashCandidate.kind === 'candidate' ? 'text_qa' as const - : await classifyInteraction(msg.task, msg.source); - rlog.info({ interactionType, slashBypass: slashCandidate.kind === 'candidate', taskLength: msg.task.length }, 'Task classified'); + : (forceQaComputerUse || isRecordingRequested) + ? 'computer_use' as const + : await classifyInteraction(msg.task, msg.source); + + // Update QA latch: set on QA intent, clear on explicit opt-out + if (isQa && msg.conversationId) { + setQaLatch(msg.conversationId); + } + if (isOptOut && msg.conversationId) { + clearQaLatch(msg.conversationId); + } + + const config = getConfig(); + const effectiveQa = isQa || (qaLatchActive && !isOptOut); + // Recording required when: explicit flag, standalone recording request, or QA intent + config flag + const requiresRecording = msg.requiresRecording + ?? (isRecordingRequested || (effectiveQa && config.qaRecording.enforceStartBeforeActions)); + + rlog.info({ + interactionType, + slashBypass: slashCandidate.kind === 'candidate', + taskLength: msg.task.length, + isQa, + isRecordingRequested, + forceQaComputerUse, + qaLatchActive, + requiresRecording, + }, 'Task classified'); if (interactionType === 'computer_use') { // Create CU session (reuse handleCuSessionCreate logic) const sessionId = uuid(); + const targetApp = resolveComputerUseTargetAppHint(msg.task); + // strictVisualQa only for QA mode — generic recording should NOT inherit QA guardrails + const strictVisualQa = effectiveQa && requiresRecording && !!(targetApp?.bundleId || targetApp?.appName); const cuMsg: CuSessionCreate = { type: 'cu_session_create', sessionId, @@ -82,6 +119,12 @@ export async function handleTaskSubmit( screenHeight: msg.screenHeight, attachments: msg.attachments, interactionType: 'computer_use', + ...(targetApp ? { targetAppName: targetApp.appName, targetAppBundleId: targetApp.bundleId } : {}), + ...(effectiveQa ? { qaMode: true } : {}), + // reportToSessionId needed for both QA and recording-only so the video attaches back to chat + ...(requiresRecording || effectiveQa ? { reportToSessionId: msg.conversationId } : {}), + ...(requiresRecording ? { requiresRecording: true } : {}), + ...(strictVisualQa ? { strictVisualQa: true } : {}), }; handleCuSessionCreate(cuMsg, socket, ctx); @@ -89,6 +132,17 @@ export async function handleTaskSubmit( type: 'task_routed', sessionId, interactionType: 'computer_use', + ...(targetApp ? { targetAppName: targetApp.appName, targetAppBundleId: targetApp.bundleId } : {}), + ...(effectiveQa ? { qaMode: true } : {}), + // Recording config for both QA and standalone recording sessions + ...(requiresRecording || effectiveQa ? { + reportToSessionId: msg.conversationId, + retentionDays: config.qaRecording.defaultRetentionDays, + captureScope: config.qaRecording.captureScope, + includeAudio: config.qaRecording.includeAudio, + } : {}), + ...(requiresRecording ? { requiresRecording: true } : {}), + ...(strictVisualQa ? { strictVisualQa: true } : {}), }); } else { // Create text QA session and immediately start processing diff --git a/assistant/src/daemon/handlers/sessions.ts b/assistant/src/daemon/handlers/sessions.ts index 63f684d9c4e..8854f5db220 100644 --- a/assistant/src/daemon/handlers/sessions.ts +++ b/assistant/src/daemon/handlers/sessions.ts @@ -35,12 +35,15 @@ import { renderHistoryContent, mergeToolResults, pendingStandaloneSecrets, + setQaLatch, + clearQaLatch, type HandlerContext, defineHandlers, type HistoryToolCall, type HistorySurface, type ParsedHistoryMessage, } from './shared.js'; +import { detectQaIntent, detectQaOptOut } from '../qa-intent.js'; import { truncate } from '../../util/truncate.js'; export async function handleUserMessage( @@ -100,6 +103,19 @@ export async function handleUserMessage( })); return; } + + // Detect QA intent / opt-out only after the message has been accepted + // (not blocked by secret ingress and not rejected by queue). This prevents + // rejected messages from incorrectly mutating the latch. + const messageContent = msg.content ?? ''; + if (messageContent) { + if (detectQaOptOut(messageContent)) { + clearQaLatch(msg.sessionId); + } else if (detectQaIntent(messageContent)) { + setQaLatch(msg.sessionId); + } + } + if (result.queued) { const position = session.getQueueDepth(); rlog.info({ position }, 'Message queued (session busy)'); @@ -405,10 +421,12 @@ export function handleHistoryRequest( // the client, so non-video attachments always keep their inline data. const MAX_INLINE_B64_SIZE = 512 * 1024; attachments = linked.map((a) => { - const omit = a.mimeType.startsWith('video/') && a.dataBase64.length > MAX_INLINE_B64_SIZE; + const isFileBacked = a.storageKind === 'file'; + const omit = isFileBacked || (a.mimeType.startsWith('video/') && a.dataBase64.length > MAX_INLINE_B64_SIZE); // Lazily generate thumbnails for existing video attachments on first history load. - if (a.mimeType.startsWith('video/') && !a.thumbnailBase64) { + // Skip for file-backed attachments since they have no inline base64 to extract from. + if (a.mimeType.startsWith('video/') && !a.thumbnailBase64 && !isFileBacked) { const attachmentId = a.id; const base64 = a.dataBase64; silentlyWithLog( diff --git a/assistant/src/daemon/handlers/shared.ts b/assistant/src/daemon/handlers/shared.ts index 5098f0be5cb..3a5ba883a40 100644 --- a/assistant/src/daemon/handlers/shared.ts +++ b/assistant/src/daemon/handlers/shared.ts @@ -9,10 +9,47 @@ import type { ClientMessage, CuSessionCreate, ServerMessage, SessionTransportMet import type { SecretPromptResult } from '../../permissions/secret-prompter.js'; import { getConfig } from '../../config/loader.js'; import type { DebouncerMap } from '../../util/debounce.js'; +import { detectQaIntent, detectQaOptOut } from '../qa-intent.js'; +import { resolveComputerUseTargetAppHint } from '../target-app-hints.js'; import type { GuardianRuntimeContext } from '../session-runtime-assembly.js'; const log = getLogger('handlers'); +/** + * Per-conversation QA latch. When a QA intent is detected in a conversation, + * the latch is set so subsequent CU turns in that thread default to + * requiresRecording=true. Cleared when the user explicitly opts out. + */ +export const qaLatchByConversation = new Map(); + +/** + * Set the QA latch for a conversation (thread). + */ +export function setQaLatch(conversationId: string): void { + qaLatchByConversation.set(conversationId, true); +} + +/** + * Clear the QA latch for a conversation (thread). + */ +export function clearQaLatch(conversationId: string): void { + qaLatchByConversation.delete(conversationId); +} + +/** + * Clear all QA latches (called on session reset / clearAllSessions). + */ +export function clearAllQaLatches(): void { + qaLatchByConversation.clear(); +} + +/** + * Check whether the QA latch is active for a conversation. + */ +export function isQaLatchActive(conversationId: string | undefined): boolean { + return conversationId != null && (qaLatchByConversation.get(conversationId) === true); +} + export { log }; /** Debounce window for suppressing file-watcher config reloads after programmatic saves. */ @@ -109,6 +146,14 @@ export interface SessionCreateOptions { commandIntent?: { type: string; payload?: string; languageCode?: string }; } +/** Metadata stored alongside a CU session for QA workflow plumbing. */ +export interface CuSessionMetadata { + /** Origin chat session to inject results into on finalization. */ + reportToSessionId?: string; + /** Whether this CU run is a QA/test workflow. */ + qaMode?: boolean; +} + /** * Shared context that handlers need from the DaemonServer. * Keeps handlers decoupled from the server class itself. @@ -118,6 +163,7 @@ export interface HandlerContext { socketToSession: Map; cuSessions: Map; socketToCuSession: Map>; + cuSessionMetadata: Map; cuObservationParseSequence: Map; socketSandboxOverride: Map; sharedRequestTimestamps: number[]; @@ -231,6 +277,29 @@ export function wireEscalationHandler( } const cuSessionId = uuid(); + const isQa = detectQaIntent(task); + const isOptOut = detectQaOptOut(task); + const qaLatchActive = isQaLatchActive(sourceSessionId); + const targetApp = resolveComputerUseTargetAppHint(task); + const config = getConfig(); + + // Compute effective QA mode — respect opt-out for latch-based activation + const effectiveQa = isQa || (qaLatchActive && !isOptOut); + + // Determine whether recording is required: effective QA + config flag + const requiresRecording = effectiveQa && config.qaRecording.enforceStartBeforeActions; + const strictVisualQa = requiresRecording && !!(targetApp?.bundleId || targetApp?.appName); + + // Set the QA latch for this conversation when QA intent is detected + if (isQa) { + setQaLatch(sourceSessionId); + } + + // Check for explicit opt-out — clear the latch for future turns + if (isOptOut) { + clearQaLatch(sourceSessionId); + } + const cuMsg: CuSessionCreate = { type: 'cu_session_create', sessionId: cuSessionId, @@ -238,6 +307,11 @@ export function wireEscalationHandler( screenWidth, screenHeight, interactionType: 'computer_use', + reportToSessionId: sourceSessionId, + ...(targetApp ? { targetAppName: targetApp.appName, targetAppBundleId: targetApp.bundleId } : {}), + ...(effectiveQa ? { qaMode: true } : {}), + ...(requiresRecording ? { requiresRecording: true } : {}), + ...(strictVisualQa ? { strictVisualQa: true } : {}), }; handleCuSessionCreate(cuMsg, currentSocket, ctx); @@ -247,6 +321,16 @@ export function wireEscalationHandler( interactionType: 'computer_use', task, escalatedFrom: sourceSessionId, + reportToSessionId: sourceSessionId, + ...(targetApp ? { targetAppName: targetApp.appName, targetAppBundleId: targetApp.bundleId } : {}), + ...(effectiveQa ? { + qaMode: true, + retentionDays: config.qaRecording.defaultRetentionDays, + captureScope: config.qaRecording.captureScope, + includeAudio: config.qaRecording.includeAudio, + } : {}), + ...(requiresRecording ? { requiresRecording: true } : {}), + ...(strictVisualQa ? { strictVisualQa: true } : {}), }); return true; diff --git a/assistant/src/daemon/ipc-contract-inventory.json b/assistant/src/daemon/ipc-contract-inventory.json index 9d50a8d9301..1931ce10dde 100644 --- a/assistant/src/daemon/ipc-contract-inventory.json +++ b/assistant/src/daemon/ipc-contract-inventory.json @@ -22,9 +22,12 @@ "ChannelReadinessRequest", "ConfirmationResponse", "ConversationSearchRequest", + "CuAutoApproveUpdate", "CuObservation", + "CuRecordingStatus", "CuSessionAbort", "CuSessionCreate", + "CuSessionFinalized", "DeleteQueuedMessage", "DiagnosticsExportRequest", "DictationRequest", @@ -289,9 +292,12 @@ "channel_readiness", "confirmation_response", "conversation_search", + "cu_auto_approve_update", "cu_observation", + "cu_recording_status", "cu_session_abort", "cu_session_create", + "cu_session_finalized", "delete_queued_message", "diagnostics_export_request", "dictation_request", diff --git a/assistant/src/daemon/ipc-contract.ts b/assistant/src/daemon/ipc-contract.ts index 7d146db0208..c2e870c7f06 100644 --- a/assistant/src/daemon/ipc-contract.ts +++ b/assistant/src/daemon/ipc-contract.ts @@ -34,7 +34,7 @@ import type { SkillsListRequest, SkillDetailRequest, SkillsEnableRequest, Skills import type { AddTrustRule, TrustRulesList, RemoveTrustRule, UpdateTrustRule, AcceptStarterBundle } from './ipc-contract/trust.js'; import type { AppDataRequest, AppsListRequest, HomeBaseGetRequest, AppOpenRequest, SharedAppsListRequest, SharedAppDeleteRequest, ForkSharedAppRequest, BundleAppRequest, OpenBundleRequest, SignBundlePayloadResponse, GetSigningIdentityResponse, GalleryListRequest, GalleryInstallRequest, AppHistoryRequest, AppDiffRequest, AppFileAtVersionRequest, AppRestoreRequest, ShareAppCloudRequest, ShareToSlackRequest, AppUpdatePreviewRequest, AppPreviewRequest, PublishPageRequest, UnpublishPageRequest } from './ipc-contract/apps.js'; import type { SlackWebhookConfigRequest, IngressConfigRequest, VercelApiConfigRequest, TwitterIntegrationConfigRequest, TelegramConfigRequest, TwilioConfigRequest, ChannelReadinessRequest, GuardianVerificationRequest, TwitterAuthStartRequest, TwitterAuthStatusRequest, IntegrationListRequest, IntegrationConnectRequest, IntegrationDisconnectRequest, LinkOpenRequest } from './ipc-contract/integrations.js'; -import type { CuSessionCreate, CuSessionAbort, CuObservation, TaskSubmit, RideShotgunStart, RideShotgunStop, WatchObservation } from './ipc-contract/computer-use.js'; +import type { CuSessionCreate, CuSessionAbort, CuObservation, CuRecordingStatus, CuAutoApproveUpdate, CuSessionFinalized, TaskSubmit, RideShotgunStart, RideShotgunStop, WatchObservation } from './ipc-contract/computer-use.js'; import type { WorkItemsListRequest, WorkItemGetRequest, WorkItemUpdateRequest, WorkItemCompleteRequest, WorkItemDeleteRequest, WorkItemRunTaskRequest, WorkItemOutputRequest, WorkItemPreflightRequest, WorkItemApprovePermissionsRequest, WorkItemCancelRequest } from './ipc-contract/work-items.js'; import type { BrowserCDPResponse, BrowserUserClick, BrowserUserScroll, BrowserUserKeypress, BrowserInteractiveMode } from './ipc-contract/browser.js'; import type { SubagentAbortRequest, SubagentStatusRequest, SubagentMessageRequest, SubagentDetailRequest, SubagentSpawned, SubagentStatusChanged, SubagentDetailResponse } from './ipc-contract/subagents.js'; @@ -70,6 +70,7 @@ export interface SubagentEvent { event: ServerMessage; } + // === Client → Server aggregate union === export type ClientMessage = @@ -93,6 +94,9 @@ export type ClientMessage = | SandboxSetRequest | CuSessionCreate | CuSessionAbort + | CuAutoApproveUpdate + | CuRecordingStatus + | CuSessionFinalized | CuObservation | RideShotgunStart | RideShotgunStop diff --git a/assistant/src/daemon/ipc-contract/computer-use.ts b/assistant/src/daemon/ipc-contract/computer-use.ts index 6462afc117a..c8052492d62 100644 --- a/assistant/src/daemon/ipc-contract/computer-use.ts +++ b/assistant/src/daemon/ipc-contract/computer-use.ts @@ -12,6 +12,25 @@ export interface CuSessionCreate { screenHeight: number; attachments?: UserMessageAttachment[]; interactionType?: 'computer_use' | 'text_qa'; + /** Origin chat session for result injection (QA workflow). */ + reportToSessionId?: string; + /** Marks this CU run as a QA/test workflow. */ + qaMode?: boolean; + /** Optional target app name constraint for disambiguation. */ + targetAppName?: string; + /** Optional target app bundle identifier for disambiguation. */ + targetAppBundleId?: string; + /** When true, recording MUST start before any destructive action. */ + requiresRecording?: boolean; + /** When true, target app must be visually frontmost during interaction and recording must be valid. */ + strictVisualQa?: boolean; +} + +export interface CuRecordingStatus { + type: 'cu_recording_status'; + sessionId: string; + status: 'started' | 'failed' | 'stopped'; + reason?: string; } export interface CuSessionAbort { @@ -19,6 +38,32 @@ export interface CuSessionAbort { sessionId: string; } +export interface CuAutoApproveUpdate { + type: 'cu_auto_approve_update'; + sessionId: string; + enabled: boolean; +} + +export interface CuSessionFinalized { + type: 'cu_session_finalized'; + sessionId: string; + status: 'completed' | 'responded' | 'failed' | 'cancelled'; + summary: string; + stepCount: number; + recording?: { + localPath: string; + mimeType: 'video/mp4'; + sizeBytes: number; + durationMs: number; + width: number; + height: number; + captureScope: 'window' | 'display'; + includeAudio: boolean; + targetBundleId?: string; + expiresAt?: number; + }; +} + export interface CuObservation { type: 'cu_observation'; sessionId: string; @@ -42,6 +87,10 @@ export interface CuObservation { executionError?: string; axTreeBlob?: IpcBlobRef; screenshotBlob?: IpcBlobRef; + /** Name of the frontmost application at observation time. */ + frontmostAppName?: string; + /** Bundle ID of the frontmost application at observation time. */ + frontmostBundleId?: string; } export interface TaskSubmit { @@ -51,6 +100,10 @@ export interface TaskSubmit { screenHeight: number; attachments?: UserMessageAttachment[]; source?: 'voice' | 'text'; + /** When set, overrides the QA-based requiresRecording computation. */ + requiresRecording?: boolean; + /** Active conversation/thread ID — used for QA latch tracking and reportToSessionId. */ + conversationId?: string; } export interface RideShotgunStart { @@ -115,6 +168,24 @@ export interface TaskRouted { task?: string; /** Set when a text_qa session escalates to computer_use via computer_use_request_control. */ escalatedFrom?: string; + /** Whether this is a QA/test workflow session. */ + qaMode?: boolean; + /** The originating chat session ID for result injection. */ + reportToSessionId?: string; + /** Recording retention in days (from daemon config). */ + retentionDays?: number; + /** Capture scope for QA recording (from daemon config). */ + captureScope?: 'window' | 'display'; + /** Whether to include audio in QA recording (from daemon config). */ + includeAudio?: boolean; + /** Target app name for frontmost-app guard (from target-app-hints). */ + targetAppName?: string; + /** Target app bundle ID for frontmost-app guard (from target-app-hints). */ + targetAppBundleId?: string; + /** When true, recording MUST start before any destructive action. */ + requiresRecording?: boolean; + /** When true, target app must be visually frontmost during interaction and recording must be valid. */ + strictVisualQa?: boolean; } export interface RideShotgunProgress { diff --git a/assistant/src/daemon/lifecycle.ts b/assistant/src/daemon/lifecycle.ts index 7fc2d74663e..67b76f51d85 100644 --- a/assistant/src/daemon/lifecycle.ts +++ b/assistant/src/daemon/lifecycle.ts @@ -73,6 +73,7 @@ import { AgentHeartbeatService } from '../agent-heartbeat/agent-heartbeat-servic import { getEnrichmentService } from '../workspace/commit-message-enrichment-service.js'; import { reconcileCallsOnStartup } from '../calls/call-recovery.js'; import { TwilioConversationRelayProvider } from '../calls/twilio-provider.js'; +import { startRecordingCleanup, stopRecordingCleanup } from './recording-cleanup.js'; const log = getLogger('lifecycle'); @@ -573,6 +574,9 @@ export async function runDaemon(): Promise { } } + // Start periodic cleanup of expired file-backed QA recording attachments. + startRecordingCleanup(config.qaRecording.cleanupIntervalMs); + // Start workspace heartbeat service. This periodically checks all // tracked workspaces for uncommitted changes and auto-commits when // thresholds are exceeded (age > 5 min OR > 20 files changed). @@ -647,6 +651,7 @@ export async function runDaemon(): Promise { if (runtimeHttp) await runtimeHttp.stop(); await browserManager.closeAllPages(); + stopRecordingCleanup(); scheduler.stop(); memoryWorker.stop(); await qdrantManager.stop(); diff --git a/assistant/src/daemon/qa-intent.ts b/assistant/src/daemon/qa-intent.ts new file mode 100644 index 00000000000..391fa8b23f4 --- /dev/null +++ b/assistant/src/daemon/qa-intent.ts @@ -0,0 +1,99 @@ +/** + * Detect whether the user is explicitly opting out of QA/recording mode. + * Used to clear the thread-level QA latch. + */ +export function detectQaOptOut(text: string): boolean { + const lower = text.toLowerCase().trim(); + const optOutPatterns = [ + /\bstop\s+qa\s+mode\b/, + /\bno\s+recording\b/, + /\bdisable\s+recording\b/, + /\bstop\s+testing\b/, + /\bstop\s+recording\b/, + /\bturn\s+off\s+qa\b/, + /\bturn\s+off\s+recording\b/, + /\bend\s+qa\s+mode\b/, + /\bexit\s+qa\s+mode\b/, + /\bquit\s+qa\s+mode\b/, + ]; + return optOutPatterns.some(p => p.test(lower)); +} + +/** + * Detect whether a user's task text indicates a QA/test workflow. + * Uses keyword/pattern matching for v1 — can be upgraded to semantic detection later. + */ +export function detectQaIntent(taskText: string): boolean { + const lower = taskText.toLowerCase().trim(); + + // Direct QA/test commands — "check" excluded because it's too common + // in everyday tasks ("check my email", "check the weather"). + if (/^(qa|test|verify)\b/.test(lower)) return true; + + // Natural language QA patterns — intentionally narrow to avoid false positives. + const qaPatterns = [ + /\b(run|do|perform|execute)\s+(a\s+)?(qa|test|verification)\b/, + /\b(test|qa|verify)\s+(this|the|that|my)\b/, + /\b(i want to|let'?s)\s+(qa|test|verify)\b/, + /\bhelp\s+me\s+(test|qa|verify)\b/, + /\b(can you|could you|please)\s+(test|qa|verify)\b/, + /\btesting\s+(the|this|that|my)\b/, + /\bcheck\s+for\s+(bugs|errors|issues|regressions)\b/, + ]; + + return qaPatterns.some(p => p.test(lower)); +} + +/** + * Whether a QA/test request should be routed directly to foreground computer use. + * This is used to avoid classifier drift for explicit UI/app QA requests. + */ +export function shouldRouteQaToComputerUse(taskText: string): boolean { + if (!detectQaIntent(taskText)) return false; + + const lower = taskText.toLowerCase().trim(); + const guiCues = [ + /\bdesktop app\b/, + /\bapp\b/, + /\bui\b/, + /\bscreen\b/, + /\bwindow\b/, + /\bcomposer\b/, + /\bin\s+the\s+thread\b/, + /\bin\s+the\s+chat\b/, + /\bbutton\b/, + /\bclick\b/, + /\btype\s+(in|into|text)\b/, + /\btyping\b/, + /\bscroll\b/, + /\bnavigate\b/, + /\bopen\s+(the\s+)?(app|window|dialog|menu|browser)\b/, + /\bsend\s+(a\s+)?(message|button|form)\b/, + /\bworkflow\b/, + /\bbehavior\b/, + ]; + const codeTestCues = [ + /\bunit tests?\b/, + /\bintegration tests?\b/, + /\be2e tests?\b/, + /\bwrite tests?\b/, + /\btest file\b/, + /\bjest\b/, + /\bvitest\b/, + /\bpytest\b/, + /\bmocha\b/, + /\bcypress\b/, + /\bplaywright\b/, + /\bci\b/, + ]; + + const hasGuiCue = guiCues.some((pattern) => pattern.test(lower)); + if (hasGuiCue) return true; + + const hasCodeOnlyCue = codeTestCues.some((pattern) => pattern.test(lower)); + if (hasCodeOnlyCue) return false; + + // No positive GUI cue and no code-only cue — don't force CU routing. + // Absence of evidence is not evidence of GUI intent; let the classifier decide. + return false; +} diff --git a/assistant/src/daemon/recording-cleanup.ts b/assistant/src/daemon/recording-cleanup.ts new file mode 100644 index 00000000000..5e8d0050528 --- /dev/null +++ b/assistant/src/daemon/recording-cleanup.ts @@ -0,0 +1,105 @@ +/** + * Periodic cleanup of expired file-backed QA recording attachments. + * + * Runs on a configurable interval (default: every 6 hours) and also + * executes a single pass on daemon startup to catch recordings that + * expired while the daemon was offline. + */ + +import { existsSync, statSync, unlinkSync } from 'node:fs'; +import { getExpiredFileAttachments, deleteFileBackedAttachment } from '../memory/attachments-store.js'; +import { getLogger } from '../util/logger.js'; + +const log = getLogger('recording-cleanup'); + +/** + * Run a single cleanup pass: find expired file-backed attachments, + * delete their files from disk, and remove the DB rows. + * + * Returns the number of cleaned-up attachments and total bytes freed. + */ +export function runCleanupPass(): { cleaned: number; bytesFreed: number } { + const expired = getExpiredFileAttachments(); + if (expired.length === 0) { + return { cleaned: 0, bytesFreed: 0 }; + } + + let cleaned = 0; + let bytesFreed = 0; + + for (const { id, filePath } of expired) { + try { + let fileSize = 0; + + if (existsSync(filePath)) { + try { + fileSize = statSync(filePath).size; + } catch { + // If we can't stat, still try to delete + } + unlinkSync(filePath); + log.info({ attachmentId: id, filePath }, 'Deleted expired recording file'); + } else { + log.debug({ attachmentId: id, filePath }, 'Expired recording file already missing from disk'); + } + + const result = deleteFileBackedAttachment(id); + if (result === 'deleted') { + cleaned++; + bytesFreed += fileSize; + } + } catch (err) { + log.warn({ err, attachmentId: id, filePath }, 'Failed to clean up expired recording'); + } + } + + if (cleaned > 0) { + const mbFreed = (bytesFreed / (1024 * 1024)).toFixed(1); + log.info({ count: cleaned, bytesFreed, mbFreed }, `Cleaned up ${cleaned} expired QA recordings, freed ${mbFreed} MB`); + } + + return { cleaned, bytesFreed }; +} + +let cleanupTimer: ReturnType | null = null; + +/** + * Start the periodic cleanup worker. Runs one immediate pass, + * then schedules recurring passes at the configured interval. + */ +export function startRecordingCleanup(intervalMs: number): void { + // Run one pass immediately to catch anything that expired while offline + try { + runCleanupPass(); + } catch (err) { + log.warn({ err }, 'Initial recording cleanup pass failed'); + } + + // setInterval uses a 32-bit signed int internally; values above 2^31-1 ms + // (~24.8 days) wrap around and fire near-continuously. + const MAX_INTERVAL_MS = 2_147_483_647; + const safeInterval = Math.min(intervalMs, MAX_INTERVAL_MS); + + cleanupTimer = setInterval(() => { + try { + runCleanupPass(); + } catch (err) { + log.warn({ err }, 'Periodic recording cleanup pass failed'); + } + }, safeInterval); + + // Don't keep the process alive just for cleanup + cleanupTimer.unref(); + log.info({ intervalMs }, 'Recording cleanup worker started'); +} + +/** + * Stop the periodic cleanup worker. + */ +export function stopRecordingCleanup(): void { + if (cleanupTimer !== null) { + clearInterval(cleanupTimer); + cleanupTimer = null; + log.info('Recording cleanup worker stopped'); + } +} diff --git a/assistant/src/daemon/recording-intent.ts b/assistant/src/daemon/recording-intent.ts new file mode 100644 index 00000000000..dabe8261869 --- /dev/null +++ b/assistant/src/daemon/recording-intent.ts @@ -0,0 +1,23 @@ +/** + * Detect whether the user is requesting screen recording. + * This is independent of QA intent — a prompt can trigger both. + * The caller (misc.ts) merges recording intent with QA-based recording + * via the `requiresRecording` computation, so no dedup is needed here. + */ +export function detectRecordingIntent(taskText: string): boolean { + const lower = taskText.toLowerCase().trim(); + + const recordingPatterns = [ + /\brecord\s+((my|the|a)\s+)?(screen|display|desktop|session)\b/, + /\bscreen\s*record/, + /\bcapture\s+((my|the|a)\s+)?(screen|display|desktop)\b/, + /\brecord\s+(this|while|what|me)\b/, + /\bstart\s+recording\b/, + /\brecord\s+(a\s+)?video\b/, + /\bvideo\s+record/, + /\bmake\s+a\s+recording\b/, + /\btake\s+a\s+(screen\s+)?recording\b/, + ]; + + return recordingPatterns.some(p => p.test(lower)); +} diff --git a/assistant/src/daemon/server.ts b/assistant/src/daemon/server.ts index e87bb0c5588..47ba69672f7 100644 --- a/assistant/src/daemon/server.ts +++ b/assistant/src/daemon/server.ts @@ -59,6 +59,7 @@ export class DaemonServer { private socketToSession = new Map(); private cuSessions = new Map(); private socketToCuSession = new Map>(); + private cuSessionMetadata = new Map(); private connectedSockets = new Set(); private socketSandboxOverride = new Map(); private cuObservationParseSequence = new Map(); @@ -297,6 +298,7 @@ export class DaemonServer { } this.cuSessions.clear(); this.socketToCuSession.clear(); + this.cuSessionMetadata.clear(); for (const socket of this.connectedSockets) { socket.destroy(); @@ -431,6 +433,7 @@ export class DaemonServer { if (cuSessionIds) { for (const cuSessionId of cuSessionIds) { this.cuObservationParseSequence.delete(cuSessionId); + this.cuSessionMetadata.delete(cuSessionId); const cuSession = this.cuSessions.get(cuSessionId); if (cuSession) { cuSession.abort(); @@ -470,6 +473,9 @@ export class DaemonServer { } this.sessions.clear(); this.sessionOptions.clear(); + // Clear QA latches to prevent unbounded growth + const { clearAllQaLatches } = require('./handlers/shared.js'); + clearAllQaLatches(); return count; } @@ -625,6 +631,7 @@ export class DaemonServer { socketToSession: this.socketToSession, cuSessions: this.cuSessions, socketToCuSession: this.socketToCuSession, + cuSessionMetadata: this.cuSessionMetadata, cuObservationParseSequence: this.cuObservationParseSequence, socketSandboxOverride: this.socketSandboxOverride, sharedRequestTimestamps: this.sharedRequestTimestamps, diff --git a/assistant/src/daemon/target-app-hints.ts b/assistant/src/daemon/target-app-hints.ts new file mode 100644 index 00000000000..f755e0b097f --- /dev/null +++ b/assistant/src/daemon/target-app-hints.ts @@ -0,0 +1,181 @@ +export interface ComputerUseTargetAppHint { + appName: string; + bundleId?: string; +} + +/** + * Context-requiring pattern wrapper: for generic words like "notes", "mail", + * "terminal", "messages", "settings" that could appear in normal sentences, + * we require the word to appear in an app-like context. + * + * Matches patterns like: + * "open Notes", "in Terminal", "test Notes", "Notes app", + * "QA notes search", "launch Terminal" + * + * Does NOT match casual uses like "take notes" or "send mail". + */ +function contextPattern(word: string): RegExp { + // Action verbs / prepositions that signal app-intent. + // Deliberately excludes "the" — too many false positives + // ("the settings in the config", "the messages carefully"). + return new RegExp( + `(?:(?:(?:^|\\b)(?:open|launch|switch\\s+to|in|test|qa|check|use)\\s+)${word}|\\b${word}\\s+app)\\b`, + 'i', + ); +} + +interface AppHintEntry { + patterns: RegExp[]; + appName: string; + bundleId: string; +} + +/** + * Ordered table of app hints. Entries are checked top-to-bottom; first match wins. + * More specific apps (Vellum) come before generic ones. + * + * For unique app names (Slack, Chrome, Discord, etc.), simple word-boundary + * matching is sufficient. For generic words (notes, mail, terminal, messages, + * settings), we use `contextPattern` to avoid false positives like + * "take notes about the meeting" or "send mail to Bob". + */ +export const APP_HINTS: AppHintEntry[] = [ + // Vellum (our app — highest priority) + { + patterns: [/\b(vellum|velly)\s+(desktop\s+)?app\b/, /\b(vellum|velly)\s+assistant\b/, /\bvellum\b/i], + appName: 'Vellum', + bundleId: 'com.vellum.vellum-assistant', + }, + // Browsers + { + patterns: [/\bchrome\b/, /\bgoogle\s+chrome\b/], + appName: 'Google Chrome', + bundleId: 'com.google.Chrome', + }, + { + patterns: [/\bsafari\b/], + appName: 'Safari', + bundleId: 'com.apple.Safari', + }, + { + patterns: [/\bfirefox\b/], + appName: 'Firefox', + bundleId: 'org.mozilla.firefox', + }, + { + patterns: [/\barc\s+browser\b/], + appName: 'Arc', + bundleId: 'company.thebrowser.Browser', + }, + // Communication + { + patterns: [/\bslack\b/], + appName: 'Slack', + bundleId: 'com.tinyspeck.slackmacgap', + }, + { + patterns: [/\bdiscord\b/], + appName: 'Discord', + bundleId: 'com.hnc.Discord', + }, + { + patterns: [contextPattern('zoom')], + appName: 'zoom.us', + bundleId: 'us.zoom.xos', + }, + { + patterns: [/\bmicrosoft\s+teams\b/, /\bteams\s+app\b/], + appName: 'Microsoft Teams', + bundleId: 'com.microsoft.teams2', + }, + // Terminals + { + patterns: [/\bwarp\b/], + appName: 'Warp', + bundleId: 'dev.warp.Warp-Stable', + }, + { + patterns: [/\biterm2?\b/], + appName: 'iTerm', + bundleId: 'com.googlecode.iterm2', + }, + { + patterns: [contextPattern('terminal')], + appName: 'Terminal', + bundleId: 'com.apple.Terminal', + }, + // IDEs + { + patterns: [/\b(vs\s*code|visual\s+studio\s+code)\b/], + appName: 'Visual Studio Code', + bundleId: 'com.microsoft.VSCode', + }, + { + patterns: [contextPattern('cursor')], + appName: 'Cursor', + bundleId: 'com.todesktop.230313mzl4w4u92', + }, + { + patterns: [/\bxcode\b/], + appName: 'Xcode', + bundleId: 'com.apple.dt.Xcode', + }, + // Productivity + { + patterns: [/\bnotion\b/], + appName: 'Notion', + bundleId: 'notion.id', + }, + { + patterns: [/\bfigma\b/], + appName: 'Figma', + bundleId: 'com.figma.Desktop', + }, + { + patterns: [/\bfinder\b/], + appName: 'Finder', + bundleId: 'com.apple.finder', + }, + // Apple apps (generic words — require context) + { + patterns: [contextPattern('notes')], + appName: 'Notes', + bundleId: 'com.apple.Notes', + }, + { + patterns: [contextPattern('messages'), /\bimessage\b/], + appName: 'Messages', + bundleId: 'com.apple.MobileSMS', + }, + { + patterns: [contextPattern('mail')], + appName: 'Mail', + bundleId: 'com.apple.mail', + }, + { + patterns: [/\bsystem\s+settings\b/, /\bsystem\s+preferences\b/, contextPattern('settings')], + appName: 'System Settings', + bundleId: 'com.apple.systempreferences', + }, +]; + +/** + * Resolve an explicit target app hint from user task text. + * This is intentionally conservative: only high-confidence patterns should + * lock the CU session to an app. + * + * Iterates through APP_HINTS in order; returns the first match. + */ +export function resolveComputerUseTargetAppHint(task: string): ComputerUseTargetAppHint | undefined { + const normalized = task.toLowerCase(); + + for (const entry of APP_HINTS) { + for (const pattern of entry.patterns) { + if (pattern.test(normalized)) { + return { appName: entry.appName, bundleId: entry.bundleId }; + } + } + } + + return undefined; +} diff --git a/assistant/src/memory/attachments-store.ts b/assistant/src/memory/attachments-store.ts index 809fc37f8a3..1b76315d692 100644 --- a/assistant/src/memory/attachments-store.ts +++ b/assistant/src/memory/attachments-store.ts @@ -17,6 +17,10 @@ export interface StoredAttachment { sizeBytes: number; kind: string; thumbnailBase64: string | null; + storageKind: 'inline_base64' | 'file'; + filePath: string | null; + sha256: string | null; + expiresAt: number | null; createdAt: number; } @@ -182,6 +186,10 @@ export function uploadAttachment( sizeBytes: attachments.sizeBytes, kind: attachments.kind, thumbnailBase64: attachments.thumbnailBase64, + storageKind: attachments.storageKind, + filePath: attachments.filePath, + sha256: attachments.sha256, + expiresAt: attachments.expiresAt, createdAt: attachments.createdAt, }) .from(attachments) @@ -189,7 +197,7 @@ export function uploadAttachment( .get(); if (existing) { - return existing; + return { ...existing, storageKind: existing.storageKind as 'inline_base64' | 'file' }; } const now = Date.now(); @@ -215,6 +223,10 @@ export function uploadAttachment( sizeBytes, kind, thumbnailBase64: null, + storageKind: 'inline_base64' as const, + filePath: null, + sha256: null, + expiresAt: null, createdAt: now, }; } @@ -281,6 +293,10 @@ export function getAttachmentsByIds( sizeBytes: row.sizeBytes, kind: row.kind, thumbnailBase64: row.thumbnailBase64, + storageKind: row.storageKind as 'inline_base64' | 'file', + filePath: row.filePath, + sha256: row.sha256, + expiresAt: row.expiresAt, dataBase64: row.dataBase64, createdAt: row.createdAt, }); @@ -356,12 +372,16 @@ export function getAttachmentMetadataForMessage( sizeBytes: attachments.sizeBytes, kind: attachments.kind, thumbnailBase64: attachments.thumbnailBase64, + storageKind: attachments.storageKind, + filePath: attachments.filePath, + sha256: attachments.sha256, + expiresAt: attachments.expiresAt, createdAt: attachments.createdAt, }) .from(attachments) .where(eq(attachments.id, link.attachmentId)) .get(); - if (row) results.push(row); + if (row) results.push({ ...row, storageKind: row.storageKind as 'inline_base64' | 'file' }); } return results; } @@ -392,3 +412,93 @@ export function deleteOrphanAttachments(candidateIds: string[]): number { ...candidateIds, ); } + +// --------------------------------------------------------------------------- +// File-backed attachment operations +// --------------------------------------------------------------------------- + +/** + * Create a file-backed attachment record. The actual file content lives on + * disk at `filePath`; the DB row stores only metadata. + */ +export function createFileBackedAttachment(params: { + filename: string; + mimeType: string; + sizeBytes: number; + filePath: string; + sha256?: string; + expiresAt?: number; + thumbnailBase64?: string; +}): StoredAttachment { + const db = getDb(); + const now = Date.now(); + const kind = classifyKind(params.mimeType); + const id = uuid(); + + const record = { + id, + originalFilename: params.filename, + mimeType: params.mimeType, + sizeBytes: params.sizeBytes, + kind, + dataBase64: '', + storageKind: 'file' as const, + filePath: params.filePath, + sha256: params.sha256 ?? null, + expiresAt: params.expiresAt ?? null, + thumbnailBase64: params.thumbnailBase64 ?? null, + createdAt: now, + }; + + db.insert(attachments).values(record).run(); + + return { + id, + originalFilename: params.filename, + mimeType: params.mimeType, + sizeBytes: params.sizeBytes, + kind, + thumbnailBase64: params.thumbnailBase64 ?? null, + storageKind: 'file', + filePath: params.filePath, + sha256: params.sha256 ?? null, + expiresAt: params.expiresAt ?? null, + createdAt: now, + }; +} + +/** + * Return file-backed attachments whose retention period has elapsed. + */ +export function getExpiredFileAttachments(): Array<{ id: string; filePath: string }> { + const db = getDb(); + const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client; + const now = Date.now(); + const rows = raw + .prepare( + `SELECT id, file_path FROM attachments WHERE storage_kind = 'file' AND expires_at IS NOT NULL AND expires_at < ?`, + ) + .all(now) as Array<{ id: string; file_path: string }>; + return rows.map((r) => ({ id: r.id, filePath: r.file_path })); +} + +/** + * Delete a file-backed attachment's DB row. The caller is responsible for + * removing the file on disk. + */ +export function deleteFileBackedAttachment(attachmentId: string): 'deleted' | 'not_found' { + const db = getDb(); + const existing = db + .select({ id: attachments.id }) + .from(attachments) + .where(eq(attachments.id, attachmentId)) + .get(); + + if (!existing) return 'not_found'; + + db.delete(attachments) + .where(eq(attachments.id, attachmentId)) + .run(); + + return 'deleted'; +} diff --git a/assistant/src/memory/db-init.ts b/assistant/src/memory/db-init.ts index 7d9116b19aa..df844f82251 100644 --- a/assistant/src/memory/db-init.ts +++ b/assistant/src/memory/db-init.ts @@ -531,6 +531,10 @@ export function initializeDb(): void { try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN memory_scope_id TEXT NOT NULL DEFAULT 'default'`); } catch { /* already exists */ } try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN origin_channel TEXT`); } catch { /* already exists */ } try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN thumbnail_base64 TEXT`); } catch { /* already exists */ } + try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN storage_kind TEXT NOT NULL DEFAULT 'inline_base64'`); } catch { /* already exists */ } + try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN file_path TEXT`); } catch { /* already exists */ } + try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN sha256 TEXT`); } catch { /* already exists */ } + try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN expires_at INTEGER`); } catch { /* already exists */ } try { database.run(/*sql*/ `ALTER TABLE cron_jobs ADD COLUMN schedule_syntax TEXT NOT NULL DEFAULT 'cron'`); } catch { /* already exists */ } try { database.run(/*sql*/ `ALTER TABLE messages ADD COLUMN metadata TEXT`); } catch { /* already exists */ } try { database.run(/*sql*/ `ALTER TABLE memory_embeddings ADD COLUMN content_hash TEXT`); } catch { /* already exists */ } diff --git a/assistant/src/memory/schema-migration.ts b/assistant/src/memory/schema-migration.ts index f1665a1498c..5397a8b3dea 100644 --- a/assistant/src/memory/schema-migration.ts +++ b/assistant/src/memory/schema-migration.ts @@ -788,12 +788,18 @@ export function migrateRemoveAssistantIdColumns(database: Db): void { data_base64 TEXT NOT NULL, content_hash TEXT, thumbnail_base64 TEXT, - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL, + storage_kind TEXT NOT NULL DEFAULT 'inline_base64', + file_path TEXT, + sha256 TEXT, + expires_at INTEGER ) `); raw.exec(/*sql*/ ` - INSERT INTO attachments_new (id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at) - SELECT id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at FROM attachments + INSERT INTO attachments_new (id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at, storage_kind, file_path, sha256, expires_at) + SELECT id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at, + COALESCE(storage_kind, 'inline_base64'), file_path, sha256, expires_at + FROM attachments `); raw.exec(/*sql*/ `DROP TABLE attachments`); raw.exec(/*sql*/ `ALTER TABLE attachments_new RENAME TO attachments`); diff --git a/assistant/src/memory/schema.ts b/assistant/src/memory/schema.ts index 3c9d29de071..160998e8fd7 100644 --- a/assistant/src/memory/schema.ts +++ b/assistant/src/memory/schema.ts @@ -167,6 +167,10 @@ export const attachments = sqliteTable('attachments', { dataBase64: text('data_base64').notNull(), contentHash: text('content_hash'), thumbnailBase64: text('thumbnail_base64'), + storageKind: text('storage_kind').notNull().default('inline_base64'), + filePath: text('file_path'), + sha256: text('sha256'), + expiresAt: integer('expires_at'), createdAt: integer('created_at').notNull(), }); diff --git a/assistant/src/runtime/http-server.ts b/assistant/src/runtime/http-server.ts index a61b10e887a..a2b94ca3a26 100644 --- a/assistant/src/runtime/http-server.ts +++ b/assistant/src/runtime/http-server.ts @@ -35,6 +35,7 @@ import { handleUploadAttachment, handleDeleteAttachment, handleGetAttachment, + handleGetAttachmentContent, } from './routes/attachment-routes.js'; import { handleCreateRun, @@ -743,6 +744,12 @@ export class RuntimeHttpServer { return await handleDeleteAttachment(req); } + // Match attachments/:attachmentId/content — must come before the generic attachments/:attachmentId + const attachmentContentMatch = endpoint.match(/^attachments\/([^/]+)\/content$/); + if (attachmentContentMatch && req.method === 'GET') { + return handleGetAttachmentContent(attachmentContentMatch[1], req); + } + // Match attachments/:attachmentId const attachmentMatch = endpoint.match(/^attachments\/([^/]+)$/); if (attachmentMatch && req.method === 'GET') { diff --git a/assistant/src/runtime/routes/attachment-routes.ts b/assistant/src/runtime/routes/attachment-routes.ts index 2471c36b9ca..4b65805cf0a 100644 --- a/assistant/src/runtime/routes/attachment-routes.ts +++ b/assistant/src/runtime/routes/attachment-routes.ts @@ -1,6 +1,7 @@ /** * Route handlers for attachment upload, download, and deletion. */ +import { existsSync, statSync } from 'node:fs'; import * as attachmentsStore from '../../memory/attachments-store.js'; import { validateAttachmentUpload, AttachmentUploadError } from '../../memory/attachments-store.js'; @@ -131,3 +132,101 @@ export function handleGetAttachment(attachmentId: string): Response { data: attachment.dataBase64, }); } + +/** + * Stream attachment content as binary. Supports Range requests for video seek. + * + * For file-backed attachments: reads from disk with optional partial content. + * For inline_base64 attachments: decodes the base64 data and returns it. + */ +export function handleGetAttachmentContent(attachmentId: string, req: Request): Response { + const attachment = attachmentsStore.getAttachmentById(attachmentId); + if (!attachment) { + return Response.json({ error: 'Attachment not found' }, { status: 404 }); + } + + if (attachment.storageKind === 'file') { + return handleFileContent(attachment, req); + } + + // inline_base64 fallback — decode and return full content + const buffer = Buffer.from(attachment.dataBase64, 'base64'); + return new Response(buffer, { + status: 200, + headers: { + 'Content-Type': attachment.mimeType, + 'Content-Length': String(buffer.length), + 'Accept-Ranges': 'bytes', + 'Content-Disposition': 'inline', + }, + }); +} + +/** + * Serve file-backed attachment content with Range header support. + */ +function handleFileContent( + attachment: attachmentsStore.StoredAttachment & { dataBase64: string }, + req: Request, +): Response { + const filePath = attachment.filePath; + if (!filePath || !existsSync(filePath)) { + return Response.json({ error: 'Attachment file not found on disk' }, { status: 404 }); + } + + let fileSize: number; + try { + fileSize = statSync(filePath).size; + } catch { + return Response.json({ error: 'Failed to read attachment file' }, { status: 500 }); + } + + const rangeHeader = req.headers.get('range'); + if (!rangeHeader) { + // Full file response + const file = Bun.file(filePath); + return new Response(file, { + status: 200, + headers: { + 'Content-Type': attachment.mimeType, + 'Content-Length': String(fileSize), + 'Accept-Ranges': 'bytes', + 'Content-Disposition': 'inline', + }, + }); + } + + // Parse Range header (only supports single byte ranges) + const rangeMatch = rangeHeader.match(/^bytes=(\d+)-(\d*)$/); + if (!rangeMatch) { + return new Response('Invalid Range header', { + status: 416, + headers: { 'Content-Range': `bytes */${fileSize}` }, + }); + } + + const start = parseInt(rangeMatch[1], 10); + const end = rangeMatch[2] ? Math.min(parseInt(rangeMatch[2], 10), fileSize - 1) : fileSize - 1; + + if (start >= fileSize || start > end) { + return new Response('Range not satisfiable', { + status: 416, + headers: { 'Content-Range': `bytes */${fileSize}` }, + }); + } + + const contentLength = end - start + 1; + const file = Bun.file(filePath); + const slice = file.slice(start, end + 1); + + return new Response(slice, { + status: 206, + headers: { + 'Content-Type': attachment.mimeType, + 'Content-Length': String(contentLength), + 'Content-Range': `bytes ${start}-${end}/${fileSize}`, + 'Accept-Ranges': 'bytes', + 'Content-Disposition': 'inline', + }, + }); +} diff --git a/assistant/src/tools/assets/search.ts b/assistant/src/tools/assets/search.ts index 465dd8ad8e7..d8d08af0744 100644 --- a/assistant/src/tools/assets/search.ts +++ b/assistant/src/tools/assets/search.ts @@ -189,11 +189,15 @@ export function searchAttachments(params: AssetSearchParams): StoredAttachment[] size_bytes: number; kind: string; thumbnail_base64: string | null; + storage_kind: string; + file_path: string | null; + sha256: string | null; + expires_at: number | null; created_at: number; } const rows = rawAll( - `SELECT a.id, a.original_filename, a.mime_type, a.size_bytes, a.kind, a.thumbnail_base64, a.created_at + `SELECT a.id, a.original_filename, a.mime_type, a.size_bytes, a.kind, a.thumbnail_base64, a.storage_kind, a.file_path, a.sha256, a.expires_at, a.created_at FROM attachments a WHERE ${whereParts.join(' AND ')} ORDER BY a.created_at DESC @@ -208,6 +212,10 @@ export function searchAttachments(params: AssetSearchParams): StoredAttachment[] sizeBytes: r.size_bytes, kind: r.kind, thumbnailBase64: r.thumbnail_base64, + storageKind: r.storage_kind as 'inline_base64' | 'file', + filePath: r.file_path, + sha256: r.sha256, + expiresAt: r.expires_at, createdAt: r.created_at, })); } @@ -224,16 +232,25 @@ export function searchAttachments(params: AssetSearchParams): StoredAttachment[] sizeBytes: attachments.sizeBytes, kind: attachments.kind, thumbnailBase64: attachments.thumbnailBase64, + storageKind: attachments.storageKind, + filePath: attachments.filePath, + sha256: attachments.sha256, + expiresAt: attachments.expiresAt, createdAt: attachments.createdAt, }) .from(attachments) .orderBy(desc(attachments.createdAt)) .limit(limit); + const castRow = (r: { storageKind: string } & Omit): StoredAttachment => ({ + ...r, + storageKind: r.storageKind as 'inline_base64' | 'file', + }); + if (where) { - return query.where(where).all(); + return query.where(where).all().map(castRow); } - return query.all(); + return query.all().map(castRow); } // --------------------------------------------------------------------------- diff --git a/assistant/src/tools/computer-use/definitions.ts b/assistant/src/tools/computer-use/definitions.ts index b2c5b93c566..39d971d7652 100644 --- a/assistant/src/tools/computer-use/definitions.ts +++ b/assistant/src/tools/computer-use/definitions.ts @@ -302,6 +302,10 @@ export const computerUseOpenAppTool: Tool = { type: 'string', description: 'The name of the application to open (e.g. "Slack", "Safari", "Google Chrome", "VS Code")', }, + app_bundle_id: { + type: 'string', + description: 'Bundle identifier of the app (e.g. com.apple.Safari). If provided, used for precise app activation.', + }, reasoning: { type: 'string', description: 'Explanation of why you need to open or switch to this app', diff --git a/cli/src/lib/local.ts b/cli/src/lib/local.ts index 3d3e422aeac..eb60b4807d6 100644 --- a/cli/src/lib/local.ts +++ b/cli/src/lib/local.ts @@ -193,6 +193,7 @@ export async function startLocalDaemon(): Promise { for (const key of [ "ANTHROPIC_API_KEY", "BASE_DATA_DIR", + "RUNTIME_HTTP_PORT", "VELLUM_DAEMON_TCP_PORT", "VELLUM_DAEMON_TCP_HOST", "VELLUM_DAEMON_SOCKET", diff --git a/clients/macos/vellum-assistant/App/AppDelegate+MenuBar.swift b/clients/macos/vellum-assistant/App/AppDelegate+MenuBar.swift index c4dd5a87776..0384a86183a 100644 --- a/clients/macos/vellum-assistant/App/AppDelegate+MenuBar.swift +++ b/clients/macos/vellum-assistant/App/AppDelegate+MenuBar.swift @@ -250,6 +250,11 @@ extension AppDelegate { menu.addItem(galleryItem) #endif + let restartItem = NSMenuItem(title: "Restart", action: #selector(performRestart), keyEquivalent: "") + restartItem.target = self + restartItem.image = NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: nil) + menu.addItem(restartItem) + menu.addItem(NSMenuItem.separator()) let logoutItem = NSMenuItem(title: "Sign Out", action: #selector(performLogout), keyEquivalent: "") diff --git a/clients/macos/vellum-assistant/App/AppDelegate+Sessions.swift b/clients/macos/vellum-assistant/App/AppDelegate+Sessions.swift index 639a38f08ca..74415fb558d 100644 --- a/clients/macos/vellum-assistant/App/AppDelegate+Sessions.swift +++ b/clients/macos/vellum-assistant/App/AppDelegate+Sessions.swift @@ -6,6 +6,101 @@ private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum. extension AppDelegate { + // MARK: - Computer Use Post-Run UX + + /// Return a user-facing explanation when CU ended without a successful completion/response. + /// This avoids the "silent disappear" feeling when the overlay auto-closes. + private func computerUseEndMessage(for session: ComputerUseSession) -> String? { + let state = session.state + + func completionLooksUnsuccessful(_ text: String) -> Bool { + let lower = text.lowercased() + let signals = [ + "wasn't able", + "couldn't", + "could not", + "unable", + "permission denied", + "denied", + "not able", + "failed", + ] + return signals.contains { lower.contains($0) } + } + + let baseMessage: String? = switch state { + case .completed(let summary, _): + if completionLooksUnsuccessful(summary) { + "Computer control finished with warnings: \(summary.replacingOccurrences(of: "\n", with: " "))" + } else { + nil + } + case .responded(let answer, _): + if completionLooksUnsuccessful(answer) { + "Computer control finished with warnings: \(answer.replacingOccurrences(of: "\n", with: " "))" + } else { + nil + } + case .failed(let reason): + "Computer control stopped: \(reason)" + case .cancelled: + "Computer control was cancelled." + case .awaitingConfirmation(let reason): + "Computer control stopped while waiting for confirmation: \(reason)" + case .running, .thinking, .paused, .idle: + "Computer control ended unexpectedly before finishing the task." + } + + if let recordingWarning = session.qaRecordingWarningMessage { + if let baseMessage { + return "\(baseMessage) Recording warning: \(recordingWarning)" + } + return "Computer control finished with warnings: \(recordingWarning)" + } + + return baseMessage + } + + // MARK: - External App Target Detection + + /// Returns `true` when the CU session targets an external app (not Vellum + /// itself). Uses bundle ID for precise comparison when available, falls back + /// to name-based check for sessions where only the app name is set. + /// When both are nil the session has no target constraint and is treated as + /// "self" (backward compatibility). + private func isExternalAppTarget(bundleId: String?, name: String?) -> Bool { + // If we have a bundleId, use precise comparison + if let bundleId, !bundleId.isEmpty { + let selfBundleId = Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant" + return bundleId != selfBundleId + } + // If we only have a name, check it's not Vellum + if let name, !name.isEmpty { + let lower = name.lowercased() + return lower != "vellum" && lower != "vellum assistant" + } + return false + } + + // MARK: - Screen Recording Permission + + /// Poll for screen recording permission after prompting, giving the user time to grant it in System Settings. + private func waitForScreenRecordingPermission() async -> Bool { + // Already granted — no need to prompt or poll + if PermissionManager.screenRecordingStatus() == .granted { return true } + + // Show the OS prompt / open System Settings + PermissionManager.requestScreenRecordingAccess() + + // Poll every 500ms for up to 30 seconds + for _ in 0..<60 { + try? await Task.sleep(nanoseconds: 500_000_000) + if Task.isCancelled { return false } + if PermissionManager.screenRecordingStatus() == .granted { return true } + } + return false + } + // MARK: - Accessibility Permission /// Poll for accessibility permission after prompting, giving the user time to grant it in System Settings. @@ -45,6 +140,23 @@ extension AppDelegate { return } + // Screen recording permission gate — only for QA/recording sessions + if routed.qaMode == true || routed.requiresRecording == true { + guard await waitForScreenRecordingPermission() else { + log.error("Screen Recording permission denied — cannot start QA session \(routed.sessionId)") + do { + try daemonClient.send(CuSessionAbortMessage(sessionId: routed.sessionId)) + } catch { + log.error("Failed to send CU session abort for escalation \(routed.sessionId): \(error)") + } + self.mainWindow?.windowState.showToast( + message: "Computer control requires Screen Recording permission. Grant it in System Settings → Privacy & Security → Screen Recording.", + style: .error + ) + return + } + } + let storedMaxSteps = UserDefaults.standard.integer(forKey: "maxStepsPerSession") let maxSteps = storedMaxSteps > 0 ? storedMaxSteps : 50 let session = ComputerUseSession( @@ -53,7 +165,17 @@ extension AppDelegate { maxSteps: maxSteps, sessionId: routed.sessionId, skipSessionCreate: true, - notificationService: self.services.activityNotificationService + notificationService: self.services.activityNotificationService, + screenRecorder: (routed.qaMode == true || routed.requiresRecording == true) ? ScreenRecorder() : nil, + reportToSessionId: routed.reportToSessionId, + qaMode: routed.qaMode ?? false, + retentionDays: routed.retentionDays.flatMap { Int($0) } ?? 7, + captureScope: routed.captureScope ?? "display", + includeAudio: routed.includeAudio ?? false, + requiresRecording: routed.requiresRecording ?? false, + targetAppName: routed.targetAppName, + targetAppBundleId: routed.targetAppBundleId, + strictVisualQa: routed.strictVisualQa ?? false ) // Don't bind relatedViewModel for escalated sessions — the active view model // may be unrelated if the user switched threads. Tool calls for escalated @@ -70,21 +192,31 @@ extension AppDelegate { self.textResponseWindow?.close() self.textResponseWindow = nil - // Hide main window so the target app becomes frontmost for CU - let mainWindowWasVisible = self.mainWindow?.isVisible ?? false - if mainWindowWasVisible { - self.mainWindow?.hide() + // Keep the main app visible during escalated CU so permission prompts + // and status are always visible to the user — but only when the + // target app IS Vellum itself (or unspecified). For external-app QA + // sessions (e.g., targeting Slack, Chrome), activating Vellum's main + // window would steal focus from the app under test. + if !self.isExternalAppTarget(bundleId: routed.targetAppBundleId, name: routed.targetAppName) { + self.showMainWindow() } + // Suppress title-bar minimize during focus-lock sessions targeting Vellum + let suppressMinimize = session.strictVisualQa && !self.isExternalAppTarget(bundleId: routed.targetAppBundleId, name: routed.targetAppName) + if suppressMinimize { self.mainWindow?.setSuppressMinimize(true) } + await session.run() + + if suppressMinimize { self.mainWindow?.setSuppressMinimize(false) } + let endMessage = self.computerUseEndMessage(for: session) try? await Task.sleep(nanoseconds: 10_000_000_000) overlay.close() self.overlayWindow = nil self.currentSession = nil self.currentTextSession = nil self.ambientAgent.resume() - if mainWindowWasVisible { - self.mainWindow?.show() + if let endMessage { + self.mainWindow?.windowState.showToast(message: endMessage, style: .error) } } } @@ -136,13 +268,17 @@ extension AppDelegate { extractedText: $0.extractedText ) } + // Pass the active thread's conversation ID so the daemon can set reportToSessionId for QA sessions + let activeConversationId = self.mainWindow?.threadManager.activeViewModel?.sessionId + do { try self.daemonClient.send(TaskSubmitMessage( task: effectiveTask, screenWidth: Int(screenBounds.width), screenHeight: Int(screenBounds.height), attachments: ipcAttachments, - source: submission.source + source: submission.source, + conversationId: activeConversationId )) } catch { log.error("Failed to send task submit message: \(error)") @@ -184,6 +320,18 @@ extension AppDelegate { } return } + // Screen recording permission gate — only for QA/recording sessions + if routed.qaMode == true || routed.requiresRecording == true { + guard await self.waitForScreenRecordingPermission() else { + log.error("Screen Recording permission denied — cannot start QA session \(routed.sessionId)") + do { + try self.daemonClient.send(CuSessionAbortMessage(sessionId: routed.sessionId)) + } catch { + log.error("Failed to send CU session abort for \(routed.sessionId): \(error)") + } + return + } + } let storedMaxSteps = UserDefaults.standard.integer(forKey: "maxStepsPerSession") let maxSteps = storedMaxSteps > 0 ? storedMaxSteps : 50 let session = ComputerUseSession( @@ -193,7 +341,17 @@ extension AppDelegate { attachments: submission.attachments, sessionId: routed.sessionId, skipSessionCreate: true, - notificationService: self.services.activityNotificationService + notificationService: self.services.activityNotificationService, + screenRecorder: (routed.qaMode == true || routed.requiresRecording == true) ? ScreenRecorder() : nil, + reportToSessionId: routed.reportToSessionId, + qaMode: routed.qaMode ?? false, + retentionDays: routed.retentionDays.flatMap { Int($0) } ?? 7, + captureScope: routed.captureScope ?? "display", + includeAudio: routed.includeAudio ?? false, + requiresRecording: routed.requiresRecording ?? false, + targetAppName: routed.targetAppName, + targetAppBundleId: routed.targetAppBundleId, + strictVisualQa: routed.strictVisualQa ?? false ) // Don't bind relatedViewModel — sessions started via startSession() don't // originate from a chat thread, so there's no ChatViewModel to extract @@ -203,12 +361,35 @@ extension AppDelegate { overlay.show() self.overlayWindow = overlay self.ambientAgent.pause() + let looksLikeQaTask = effectiveTask.localizedCaseInsensitiveContains("qa") + || effectiveTask.localizedCaseInsensitiveContains("test") + || effectiveTask.localizedCaseInsensitiveContains("verify") + if routed.qaMode == true || looksLikeQaTask { + // QA/test CU sessions should keep the main app open — + // but only when the target app is Vellum itself (or + // unspecified). For external-app QA sessions, activating + // Vellum would steal focus from the app under test. + if !self.isExternalAppTarget(bundleId: routed.targetAppBundleId, name: routed.targetAppName) { + self.showMainWindow() + } + } + + // Suppress title-bar minimize during focus-lock sessions targeting Vellum + let suppressMinimize = session.strictVisualQa && !self.isExternalAppTarget(bundleId: routed.targetAppBundleId, name: routed.targetAppName) + if suppressMinimize { self.mainWindow?.setSuppressMinimize(true) } + await session.run() + + if suppressMinimize { self.mainWindow?.setSuppressMinimize(false) } + let endMessage = self.computerUseEndMessage(for: session) try? await Task.sleep(nanoseconds: 10_000_000_000) overlay.close() self.overlayWindow = nil self.currentSession = nil self.ambientAgent.resume() + if let endMessage { + self.mainWindow?.windowState.showToast(message: endMessage, style: .error) + } default: // text_qa let session = TextSession( diff --git a/clients/macos/vellum-assistant/App/AppDelegate.swift b/clients/macos/vellum-assistant/App/AppDelegate.swift index df71e9ca5d5..5822a8806ce 100644 --- a/clients/macos/vellum-assistant/App/AppDelegate.swift +++ b/clients/macos/vellum-assistant/App/AppDelegate.swift @@ -322,6 +322,21 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { authWindow = window } + @objc func performRestart() { + // Stop daemon before relaunch + assistantCli.stop() + + // Launch a fresh instance then quit + let bundleURL = Bundle.main.bundleURL + let config = NSWorkspace.OpenConfiguration() + config.createsNewApplicationInstance = true + NSWorkspace.shared.openApplication(at: bundleURL, configuration: config) { _, _ in + DispatchQueue.main.async { + NSApp.terminate(nil) + } + } + } + @objc func performLogout() { Task { await authManager.logout() @@ -557,12 +572,11 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { let assistant = loadAssistantFromLockfile() - // Ensure the daemon starts its runtime HTTP server so the app - // can communicate over HTTP instead of IPC. - if FeatureFlagManager.shared.isEnabled(.localHttpEnabled) { - if ProcessInfo.processInfo.environment["RUNTIME_HTTP_PORT"] == nil { - setenv("RUNTIME_HTTP_PORT", "7821", 0) - } + // Ensure the daemon always starts its runtime HTTP server. + // Required for file-backed attachment content streaming (video playback), + // and optionally used for HTTP-based IPC transport when localHttpEnabled is on. + if ProcessInfo.processInfo.environment["RUNTIME_HTTP_PORT"] == nil { + setenv("RUNTIME_HTTP_PORT", "7821", 0) } configureDaemonTransport(for: assistant) @@ -892,8 +906,10 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { guard let self else { return } Task { @MainActor in // Auto-approve low/medium risk tool confirmations during CU sessions + // (reactive fallback — daemon-side proactive skip handles most cases) + let normalizedRisk = msg.riskLevel.lowercased() if self.currentSession?.autoApproveTools == true, - msg.riskLevel == "low" || msg.riskLevel == "medium" { + normalizedRisk == "low" || normalizedRisk == "medium" { do { try self.daemonClient.sendConfirmationResponse( requestId: msg.requestId, @@ -909,18 +925,35 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { return } + // Active CU sessions do not have reliable inline-chat confirmation UX. + // Always surface permission prompts directly in the CU overlay. + if let currentSession = self.currentSession { + currentSession.presentToolPermissionPrompt(msg) + self.overlayWindow?.bringToFront() + return + } + // When the chat window is visible AND the confirmation belongs to the // active thread, the inline ToolConfirmationBubble handles the // confirmation UX — skip the native notification to avoid showing a // duplicate prompt. If the confirmation is for a background thread, // the inline bubble won't be visible, so we must still fire the // native notification. - if NSApp.isActive, let mainWindow = self.mainWindow, mainWindow.isVisible { - let activeSessionId = mainWindow.threadManager.activeViewModel?.sessionId - let confirmationIsForActiveThread = msg.sessionId == nil || msg.sessionId == activeSessionId - if confirmationIsForActiveThread { - return - } + // + // Important: do NOT treat a nil sessionId as "active thread". CU + // sessions may omit the sessionId, and suppressing their prompts + // causes the permission request to time out and auto-deny silently. + // Also skip suppression entirely when a CU session is running — + // CU prompts are never handled by the inline chat bubble. + if NSApp.isActive, + let mainWindow = self.mainWindow, + mainWindow.isVisible, + let promptSessionId = msg.sessionId, + let activeSessionId = mainWindow.threadManager.activeViewModel?.sessionId, + promptSessionId == activeSessionId + { + log.info("Suppressing native notification for confirmation \(msg.requestId) — inline bubble handles it (sessionId=\(promptSessionId))") + return } let decision = await self.toolConfirmationNotificationService.showConfirmation(msg) diff --git a/clients/macos/vellum-assistant/App/AssistantCli.swift b/clients/macos/vellum-assistant/App/AssistantCli.swift index 197e44e8b2e..ac8b64294a0 100644 --- a/clients/macos/vellum-assistant/App/AssistantCli.swift +++ b/clients/macos/vellum-assistant/App/AssistantCli.swift @@ -593,6 +593,12 @@ final class AssistantCli { "HOME": NSHomeDirectory(), "PATH": fullEnv["PATH"] ?? "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin", "VELLUM_DESKTOP_APP": "1", + // Always start the HTTP server for file-backed attachment content + // streaming (video playback). ProcessInfo.processInfo.environment is + // cached at launch, so read via getenv() which reflects setenv() calls. + "RUNTIME_HTTP_PORT": fullEnv["RUNTIME_HTTP_PORT"] + ?? getenv("RUNTIME_HTTP_PORT").flatMap({ String(cString: $0) }) + ?? "7821", ] // Forward optional config vars the CLI or daemon may need for key in ["ANTHROPIC_API_KEY", "BASE_DATA_DIR", "VELLUM_DEBUG", @@ -602,12 +608,6 @@ final class AssistantCli { env[key] = val } } - // Forward RUNTIME_HTTP_PORT only when the localHttpEnabled flag - // is active, so the daemon doesn't start its HTTP server by default. - if FeatureFlagManager.shared.isEnabled(.localHttpEnabled), - let port = fullEnv["RUNTIME_HTTP_PORT"] ?? getenv("RUNTIME_HTTP_PORT").flatMap({ String(cString: $0) }) { - env["RUNTIME_HTTP_PORT"] = port - } proc.environment = env try proc.run() diff --git a/clients/macos/vellum-assistant/App/PermissionManager.swift b/clients/macos/vellum-assistant/App/PermissionManager.swift index f80f43c7d8d..a70919e5393 100644 --- a/clients/macos/vellum-assistant/App/PermissionManager.swift +++ b/clients/macos/vellum-assistant/App/PermissionManager.swift @@ -22,18 +22,29 @@ enum PermissionManager { static func requestScreenRecordingAccess() { let hasRequestedBefore = UserDefaults.standard.bool(forKey: hasRequestedScreenRecordingFlag) + let preflightBeforeRequest = CGPreflightScreenCaptureAccess() // CGRequestScreenCaptureAccess() only shows the native OS prompt on // its very first invocation per app install; subsequent calls are // no-ops. The API is non-blocking, so CGPreflightScreenCaptureAccess() // returns false immediately — before the user has a chance to respond - // to the prompt. On the first call we therefore trust the native prompt - // and skip the System Settings fallback to avoid showing both at once. + // to the prompt. CGRequestScreenCaptureAccess() if !hasRequestedBefore { UserDefaults.standard.set(true, forKey: hasRequestedScreenRecordingFlag) - } else if !CGPreflightScreenCaptureAccess() { + // On first request we cannot distinguish between a fresh install + // (where the native prompt just appeared) and a legacy denied + // install (where CGRequestScreenCaptureAccess() was a no-op). + // In both cases CGPreflightScreenCaptureAccess() returns false. + // To avoid opening System Settings alongside the native prompt + // (double-prompt), we only open Settings when we know the user + // had already been prompted before — i.e. hasRequestedBefore was + // true. Legacy denied users will get the Settings fallback on + // their next interaction. + } else if !preflightBeforeRequest { + // Permission was already denied before this request — the native + // prompt won't appear, so open System Settings as a fallback. openScreenRecordingSettings() } } diff --git a/clients/macos/vellum-assistant/ComputerUse/AXActionExecutor.swift b/clients/macos/vellum-assistant/ComputerUse/AXActionExecutor.swift new file mode 100644 index 00000000000..2184bf4fb36 --- /dev/null +++ b/clients/macos/vellum-assistant/ComputerUse/AXActionExecutor.swift @@ -0,0 +1,106 @@ +import ApplicationServices +import AppKit +import os + +private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "AXAction") + +/// Result of an AX-first action attempt. +enum AXActionResult { + /// AX action succeeded — no CGEvent fallback needed. + case success(String?) + /// AX action could not be performed — caller should fall back to CGEvent. + case fallback(reason: String) +} + +/// Executes click and type actions via the Accessibility API instead of CGEvent injection. +/// +/// AX-first actions are more reliable than coordinate-based CGEvent because they: +/// - Target the exact UI element (no coordinate drift from window repositioning) +/// - Work even when the element is partially obscured +/// - Don't require the element to be at a specific screen position +/// +/// Falls back to CGEvent when: +/// - No element registry is available +/// - The target element doesn't support the required AX action +/// - AX API calls fail (app unresponsive, permission issues) +@MainActor +final class AXActionExecutor { + + /// Reference to the element registry for resolving elementId -> AXUIElement. + private let elementRegistry: AXElementRegistry + + init(elementRegistry: AXElementRegistry) { + self.elementRegistry = elementRegistry + } + + /// Attempts to click an element via AX kAXPressAction. + /// + /// - Parameter elementId: The AX element ID from the last enumeration. + /// - Returns: `.success` if the action was performed, `.fallback` if CGEvent should be used. + func click(elementId: Int) -> AXActionResult { + guard let axElement = elementRegistry.resolve(elementId: elementId) else { + return .fallback(reason: "Element [\(elementId)] not in registry") + } + + let result = AXUIElementPerformAction(axElement, kAXPressAction as CFString) + if result == .success { + log.info("AX click on [\(elementId)]: success") + return .success("AX click performed on element [\(elementId)]") + } + + // Some elements don't support kAXPressAction — fall back + log.info("AX click on [\(elementId)]: failed (AXError \(result.rawValue)), falling back to CGEvent") + return .fallback(reason: "kAXPressAction failed: AXError \(result.rawValue)") + } + + /// Attempts to set text on an element via AX kAXValueAttribute. + /// + /// This works for text fields and text areas that accept the AXValue attribute. + /// For elements that don't support it (e.g., content-editable web fields), + /// falls back to CGEvent keyboard input. + /// + /// - Parameters: + /// - elementId: The AX element ID from the last enumeration. + /// - text: The text to set. + /// - Returns: `.success` if the value was set, `.fallback` if CGEvent should be used. + func type(elementId: Int, text: String) -> AXActionResult { + guard let axElement = elementRegistry.resolve(elementId: elementId) else { + return .fallback(reason: "Element [\(elementId)] not in registry") + } + + // First, focus the element + let focusResult = AXUIElementSetAttributeValue(axElement, kAXFocusedAttribute as CFString, true as CFTypeRef) + if focusResult != .success { + log.info("AX type on [\(elementId)]: focus failed (AXError \(focusResult.rawValue)), falling back") + return .fallback(reason: "Could not focus element [\(elementId)]: AXError \(focusResult.rawValue)") + } + + // Try setting the value directly + let result = AXUIElementSetAttributeValue(axElement, kAXValueAttribute as CFString, text as CFTypeRef) + if result == .success { + log.info("AX type on [\(elementId)]: set value successfully (\(text.count) chars)") + return .success("AX type performed on element [\(elementId)]") + } + + log.info("AX type on [\(elementId)]: set value failed (AXError \(result.rawValue)), falling back to CGEvent") + return .fallback(reason: "kAXValueAttribute set failed: AXError \(result.rawValue)") + } + + /// Attempts to focus an element via AX. + /// + /// - Parameter elementId: The AX element ID from the last enumeration. + /// - Returns: `.success` if focused, `.fallback` if it failed. + func focus(elementId: Int) -> AXActionResult { + guard let axElement = elementRegistry.resolve(elementId: elementId) else { + return .fallback(reason: "Element [\(elementId)] not in registry") + } + + let result = AXUIElementSetAttributeValue(axElement, kAXFocusedAttribute as CFString, true as CFTypeRef) + if result == .success { + log.info("AX focus on [\(elementId)]: success") + return .success("AX focus on element [\(elementId)]") + } + + return .fallback(reason: "Could not focus element [\(elementId)]: AXError \(result.rawValue)") + } +} diff --git a/clients/macos/vellum-assistant/ComputerUse/AXElementRegistry.swift b/clients/macos/vellum-assistant/ComputerUse/AXElementRegistry.swift new file mode 100644 index 00000000000..bd733d572f8 --- /dev/null +++ b/clients/macos/vellum-assistant/ComputerUse/AXElementRegistry.swift @@ -0,0 +1,40 @@ +import ApplicationServices +import os + +private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "AXRegistry") + +/// Maps AXElement IDs (from the enumerated AX tree) back to live AXUIElement references. +/// +/// During AX tree enumeration, each element is assigned a monotonic integer ID. This registry +/// stores the original AXUIElement reference for each ID so that AX-first actions (click, type) +/// can target elements directly without coordinate conversion. +/// +/// The registry is cleared and rebuilt on each enumeration cycle, so element IDs are only valid +/// for the current step. +/// +/// Thread safety: accessed from the enumerator (synchronous, single-threaded) and from +/// `AXActionExecutor` (MainActor). All access is sequential — enumeration completes before +/// any action executor reads — so no concurrent mutation occurs. +final class AXElementRegistry: @unchecked Sendable { + /// Maps element ID -> live AXUIElement reference. + private var elements: [Int: AXUIElement] = [:] + + /// Clears all stored references. Called at the start of each enumeration. + func clear() { + elements.removeAll(keepingCapacity: true) + } + + /// Registers an AXUIElement with the given ID. + func register(elementId: Int, element: AXUIElement) { + elements[elementId] = element + } + + /// Resolves an element ID to its live AXUIElement reference. + /// Returns nil if the ID is not in the registry (e.g., stale ID from a previous step). + func resolve(elementId: Int) -> AXUIElement? { + return elements[elementId] + } + + /// The number of registered elements. + var count: Int { elements.count } +} diff --git a/clients/macos/vellum-assistant/ComputerUse/AccessibilityTree.swift b/clients/macos/vellum-assistant/ComputerUse/AccessibilityTree.swift index 29351310498..fbf4886cb6e 100644 --- a/clients/macos/vellum-assistant/ComputerUse/AccessibilityTree.swift +++ b/clients/macos/vellum-assistant/ComputerUse/AccessibilityTree.swift @@ -28,14 +28,28 @@ struct WindowInfo { protocol AccessibilityTreeProviding { func enumerateCurrentWindow() -> (elements: [AXElement], windowTitle: String, appName: String, pid: pid_t)? func enumerateSecondaryWindows(excludingPID: pid_t?, maxWindows: Int) -> [WindowInfo] + /// Optional element registry — populated during enumeration for AX-first action targeting. + var elementRegistry: AXElementRegistry? { get } + /// When true, enumerating Vellum's own window is allowed (for self-targeted CU sessions). + var allowSelfEnumeration: Bool { get } +} + +extension AccessibilityTreeProviding { + var allowSelfEnumeration: Bool { false } } final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { + /// When true, enumerating Vellum's own window is allowed instead of skipping to the previous app. + var allowSelfEnumeration: Bool = false + private var nextId = 1 /// PID of the last successfully enumerated target app, used to resolve the /// correct app when our own window is frontmost. private var lastTargetPid: pid_t? + /// Element registry for AX-first action targeting — maps elementId -> AXUIElement. + let elementRegistry: AXElementRegistry? = AXElementRegistry() + /// Track total elements enumerated in current call to prevent infinite loops private var totalElementsEnumerated = 0 /// Maximum elements to enumerate before bailing out (protects against circular refs) @@ -113,7 +127,8 @@ final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { } // Skip our own app — we want the window behind the overlay - if let myId = myBundleId, frontApp.bundleIdentifier == myId { + // Unless allowSelfEnumeration is set (self-targeted CU sessions) + if let myId = myBundleId, frontApp.bundleIdentifier == myId, !allowSelfEnumeration { log.info("Skipping own app, looking for previous app") return enumeratePreviousApp() } @@ -147,11 +162,12 @@ final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { nextId = 1 totalElementsEnumerated = 0 + elementRegistry?.clear() let elements = enumerateElementSafely(element: windowElement, depth: 0, maxDepth: 25) let flat = AccessibilityTreeEnumerator.flattenElements(elements) let interactive = flat.filter { Self.interactiveRoles.contains($0.role) } - log.info("Enumerated \(appName, privacy: .public): \(flat.count) total, \(interactive.count) interactive, maxId=\(self.nextId - 1)") + log.info("Enumerated \(appName, privacy: .public): \(flat.count) total, \(interactive.count) interactive, maxId=\(self.nextId - 1), registry=\(self.elementRegistry?.count ?? 0)") lastTargetPid = pid return (elements: elements, windowTitle: windowTitle, appName: appName, pid: pid) @@ -216,6 +232,7 @@ final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { nextId = 1 totalElementsEnumerated = 0 + elementRegistry?.clear() let elements = enumerateElementSafely(element: windowElement, depth: 0, maxDepth: 25) guard !elements.isEmpty else { return nil } @@ -335,6 +352,7 @@ final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { if isInteractive { let id = nextId nextId += 1 + elementRegistry?.register(elementId: id, element: element) return [AXElement( id: id, role: role, @@ -354,6 +372,7 @@ final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { if isStaticText && hasTextContent { let id = nextId nextId += 1 + elementRegistry?.register(elementId: id, element: element) return [AXElement( id: id, role: role, @@ -373,6 +392,7 @@ final class AccessibilityTreeEnumerator: AccessibilityTreeProviding { if isContainer && !childElements.isEmpty { let id = nextId nextId += 1 + elementRegistry?.register(elementId: id, element: element) return [AXElement( id: id, role: role, diff --git a/clients/macos/vellum-assistant/ComputerUse/ActionExecutor.swift b/clients/macos/vellum-assistant/ComputerUse/ActionExecutor.swift index 006a613d324..5e0b8e37a03 100644 --- a/clients/macos/vellum-assistant/ComputerUse/ActionExecutor.swift +++ b/clients/macos/vellum-assistant/ComputerUse/ActionExecutor.swift @@ -3,6 +3,11 @@ import AppKit import ApplicationServices import os +private let log = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", + category: "ActionExecutor" +) + enum ExecutorError: LocalizedError { case eventCreationFailed case missingCoordinates @@ -11,10 +16,12 @@ enum ExecutorError: LocalizedError { case unknownKey(String) case accessibilityNotGranted case appNotFound(String) + case appMismatch(requested: String, resolved: String) case appleScriptError(String) case appleScriptMissingScript case appleScriptTimeout case clipboardMismatch + case focusAcquireFailed(String) var errorDescription: String? { switch self { @@ -24,11 +31,13 @@ enum ExecutorError: LocalizedError { case .missingKey: return "Key action requires key name" case .unknownKey(let key): return "Unknown key: \(key)" case .accessibilityNotGranted: return "Accessibility permission not granted" - case .appNotFound(let name): return "Application not found: \(name)" + case .appNotFound(let name): return "app_not_found: \(name)" + case .appMismatch(let requested, let resolved): return "app_mismatch: requested=\(requested) resolved=\(resolved)" case .appleScriptError(let msg): return "AppleScript error: \(msg)" case .appleScriptMissingScript: return "run_applescript requires a script" case .appleScriptTimeout: return "AppleScript timed out after 5 seconds" case .clipboardMismatch: return "Clipboard contents changed before paste injection; aborting to prevent wrong text from being typed" + case .focusAcquireFailed(let reason): return "FOCUS_ACQUIRE_FAILED: \(reason)" } } } @@ -37,8 +46,6 @@ protocol ActionExecuting { func execute(_ action: AgentAction) async throws -> String? } -private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "ActionExecutor") - final class ActionExecutor: ActionExecuting { private let eventSource: CGEventSource? @@ -247,7 +254,7 @@ final class ActionExecutor: ActionExecuting { try drag(from: CGPoint(x: fromX, y: fromY), to: CGPoint(x: endX, y: endY)) case .openApp: guard let appName = action.appName else { throw ExecutorError.appNotFound("(no name)") } - try await openApp(name: appName) + try await openApp(name: appName, bundleId: action.appBundleId, requireExactMatch: action.requireExactAppMatch) case .runAppleScript: guard let source = action.script else { throw ExecutorError.appleScriptMissingScript } return try await runAppleScript(source) @@ -340,35 +347,240 @@ final class ActionExecutor: ActionExecuting { // MARK: - Open App static let appAliases: [String: String] = [ + // Browsers "chrome": "Google Chrome", + "arc": "Arc", + "ff": "Firefox", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Microsoft Edge", + // Communication + "slack": "Slack", + "discord": "Discord", + "zoom": "zoom.us", + "teams": "Microsoft Teams", + "imessage": "Messages", + "messages": "Messages", + "mail": "Mail", + // IDEs & dev tools + "code": "Visual Studio Code", "vs code": "Visual Studio Code", "vscode": "Visual Studio Code", - "edge": "Microsoft Edge", + "cursor": "Cursor", + "xcode": "Xcode", + "warp": "Warp", + "terminal": "Terminal", + "iterm": "iTerm", + // Productivity + "figma": "Figma", + "notion": "Notion", + "notes": "Notes", + "finder": "Finder", + // Microsoft Office "word": "Microsoft Word", "excel": "Microsoft Excel", "powerpoint": "Microsoft PowerPoint", "outlook": "Microsoft Outlook", - "teams": "Microsoft Teams", - "iterm": "iTerm", + // System + "settings": "System Settings", + "preferences": "System Settings", + "system preferences": "System Settings", + "system settings": "System Settings", + // Vellum + "vellum": "Vellum", + "velly": "Vellum", ] - func openApp(name: String) async throws { + /// Strips non-alphanumeric characters and lowercases for fuzzy comparison. + private static func normalizeAppName(_ value: String) -> String { + value.lowercased().filter { $0.isLetter || $0.isNumber } + } + + /// Run `mdfind` as a subprocess with a timeout, returning the first result path or nil. + private func mdfindApp(name: String, timeout: UInt64 = 2_000_000_000) async -> URL? { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/mdfind") + // Use double quotes for the display-name value. Escape backslashes first + // (Spotlight treats \ as an escape character inside double-quoted strings), + // then double quotes. Process.arguments bypasses the shell, so shell-style + // single-quote escaping (e.g. '\'') would be passed raw to mdfind and + // break Spotlight query parsing. + let sanitizedName = name.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") + process.arguments = [ + "kMDItemKind == 'Application' && kMDItemDisplayName == \"\(sanitizedName)\"" + ] + + let stdout = Pipe() + process.standardOutput = stdout + process.standardError = Pipe() // discard stderr + + return await withCheckedContinuation { continuation in + // Register termination handler BEFORE starting the process to avoid + // a race where mdfind exits before the handler is set, which would + // cause withCheckedContinuation to never resume and hang forever. + process.terminationHandler = { proc in + guard proc.terminationStatus == 0, + proc.terminationReason != .uncaughtSignal else { + continuation.resume(returning: nil) + return + } + + let data = stdout.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) + + // Take the first result line that ends in .app + if let firstLine = output?.split(separator: "\n").first, + firstLine.hasSuffix(".app") { + continuation.resume(returning: URL(fileURLWithPath: String(firstLine))) + } else { + continuation.resume(returning: nil) + } + } + + do { + try process.run() + } catch { + // process.run() failed so terminationHandler won't fire — resume manually + log.warning("openApp mdfind failed to launch: \(error.localizedDescription, privacy: .public)") + continuation.resume(returning: nil) + return + } + + // Timeout — terminate the subprocess if it takes too long + Task { + try? await Task.sleep(nanoseconds: timeout) + if process.isRunning { + process.terminate() + } + } + } + } + + /// After activation, verify the expected app became frontmost. + /// Returns true if the expected app is frontmost, false otherwise. + @discardableResult + private func verifyFrontmost(expectedName: String) async -> Bool { + try? await Task.sleep(nanoseconds: 500_000_000) // 0.5s + if let frontmost = NSWorkspace.shared.frontmostApplication, + frontmost.localizedName?.lowercased() != expectedName.lowercased() { + log.warning("openApp: app may not have focused — expected \(expectedName, privacy: .public) but frontmost is \(frontmost.localizedName ?? "unknown", privacy: .public)") + return false + } + return true + } + + func openApp(name: String, bundleId: String? = nil, requireExactMatch: Bool = false) async throws { let workspace = NSWorkspace.shared + var attemptedPaths: [String] = [] - // 1. Check running apps for exact or case-insensitive match - let nameLower = name.lowercased() - if let runningApp = workspace.runningApplications.first(where: { - $0.localizedName?.lowercased() == nameLower - }) { - runningApp.activate() - try await Task.sleep(nanoseconds: 300_000_000) // 300ms for app to come forward - return + // 1. Bundle-ID resolution — most precise, avoids name ambiguity + if let bundleId, !bundleId.isEmpty { + attemptedPaths.append("bundle-id(\(bundleId))") + if let runningApp = workspace.runningApplications.first(where: { + $0.bundleIdentifier == bundleId + }) { + log.info("openApp resolved via bundle-id (running): \(bundleId, privacy: .public)") + // For our own app: unhide + deminiaturize to handle hidden/minimized state + let selfBundleId = Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant" + if bundleId == selfBundleId { + NSApp.unhide(nil) + for window in NSApp.windows where window.isMiniaturized { + window.deminiaturize(nil) + } + } + if runningApp.isHidden { + runningApp.unhide() + } + runningApp.activate(options: [.activateIgnoringOtherApps, .activateAllWindows]) + let focused = await verifyFrontmost(expectedName: runningApp.localizedName ?? name) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(runningApp.localizedName ?? name)' but frontmost is '\(frontmost)'") + } + return + } + if let appURL = workspace.urlForApplication(withBundleIdentifier: bundleId) { + log.info("openApp resolved via bundle-id (installed): \(bundleId, privacy: .public)") + let config = NSWorkspace.OpenConfiguration() + config.activates = true + try await workspace.openApplication(at: appURL, configuration: config) + let focused = await verifyFrontmost(expectedName: name) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(name)' but frontmost is '\(frontmost)'") + } + return + } + if requireExactMatch { + log.error("openApp exact-match mode: bundle-id \(bundleId, privacy: .public) not found, refusing fuzzy fallback") + throw ExecutorError.appNotFound("\(name) (bundle: \(bundleId), exact match required)") + } + log.warning("openApp bundle-id not found, falling back to name: \(bundleId, privacy: .public)") + } else if requireExactMatch { + // In exact-match mode without a bundle ID, only allow exact name match + // against running apps — no fuzzy/substring matching + attemptedPaths.append("exact-name(\(name))") + if let runningApp = workspace.runningApplications.first(where: { + $0.localizedName == name + }) { + log.info("openApp resolved via exact name match (running): \(name, privacy: .public)") + runningApp.activate(options: [.activateIgnoringOtherApps, .activateAllWindows]) + let focused = await verifyFrontmost(expectedName: name) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(name)' but frontmost is '\(frontmost)'") + } + return + } + // Fall through to filesystem/mdfind — those are exact by nature + } + + // 2. Normalized/fuzzy name matching against running apps + // Skip fuzzy matching in exact-match mode — only bundle-ID and exact name are trusted + if !requireExactMatch { + let normalizedName = Self.normalizeAppName(name) + attemptedPaths.append("fuzzy-running(\(name))") + // Guard: when the input normalizes to empty (e.g. "---"), String.contains("") + // returns true for any string, which would match the first running app arbitrarily. + if !normalizedName.isEmpty, let runningApp = workspace.runningApplications.first(where: { app in + guard let localizedName = app.localizedName else { return false } + let normalized = Self.normalizeAppName(localizedName) + return normalized == normalizedName + || normalized.contains(normalizedName) + || normalizedName.contains(normalized) + }) { + log.info("openApp resolved via fuzzy name match (running): \(name, privacy: .public) -> \(runningApp.localizedName ?? "?", privacy: .public)") + runningApp.activate(options: [.activateIgnoringOtherApps, .activateAllWindows]) + await verifyFrontmost(expectedName: runningApp.localizedName ?? name) + return + } } - // 2. Resolve aliases + // 3. Resolve aliases + let nameLower = name.lowercased() let resolvedName = Self.appAliases[nameLower] ?? name - // 3. Search common application directories + if resolvedName != name { + attemptedPaths.append("alias(\(nameLower)->\(resolvedName))") + log.info("openApp resolved via alias: \(name, privacy: .public) -> \(resolvedName, privacy: .public)") + // Re-check running apps with the aliased name + let normalizedResolved = Self.normalizeAppName(resolvedName) + if let runningApp = workspace.runningApplications.first(where: { app in + guard let localizedName = app.localizedName else { return false } + return Self.normalizeAppName(localizedName) == normalizedResolved + }) { + runningApp.activate(options: [.activateIgnoringOtherApps, .activateAllWindows]) + let focused = await verifyFrontmost(expectedName: resolvedName) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(resolvedName)' but frontmost is '\(frontmost)'") + } + return + } + } + + // 4. Filesystem search in common application directories let searchDirs = [ "/Applications", "/System/Applications", @@ -376,29 +588,60 @@ final class ActionExecutor: ActionExecuting { NSString("~/Applications").expandingTildeInPath, ] + attemptedPaths.append("filesystem-exact(\(searchDirs.joined(separator: ",")))") for dir in searchDirs { let appPath = "\(dir)/\(resolvedName).app" let appURL = URL(fileURLWithPath: appPath) if FileManager.default.fileExists(atPath: appPath) { + log.info("openApp resolved via filesystem (exact): \(appPath, privacy: .public)") let config = NSWorkspace.OpenConfiguration() config.activates = true try await workspace.openApplication(at: appURL, configuration: config) + let focused = await verifyFrontmost(expectedName: resolvedName) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(resolvedName)' but frontmost is '\(frontmost)'") + } return } } - // 4. Try case-insensitive filesystem search in /Applications - if let found = try? FileManager.default.contentsOfDirectory(atPath: "/Applications") - .first(where: { - $0.lowercased() == "\(resolvedName.lowercased()).app" - }) { - let appURL = URL(fileURLWithPath: "/Applications/\(found)") + // 5. Case-insensitive filesystem search across all common directories + attemptedPaths.append("filesystem-case-insensitive(\(searchDirs.joined(separator: ",")))") + let targetFilename = "\(resolvedName.lowercased()).app" + for dir in searchDirs { + if let found = try? FileManager.default.contentsOfDirectory(atPath: dir) + .first(where: { $0.lowercased() == targetFilename }) { + let appURL = URL(fileURLWithPath: "\(dir)/\(found)") + log.info("openApp resolved via filesystem (case-insensitive): \(dir)/\(found, privacy: .public)") + let config = NSWorkspace.OpenConfiguration() + config.activates = true + try await workspace.openApplication(at: appURL, configuration: config) + let focused = await verifyFrontmost(expectedName: resolvedName) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(resolvedName)' but frontmost is '\(frontmost)'") + } + return + } + } + + // 6. Spotlight/mdfind fallback — catches apps in non-standard locations (e.g. Homebrew Cask) + attemptedPaths.append("mdfind(\(resolvedName))") + if let appURL = await mdfindApp(name: resolvedName) { + log.info("openApp resolved via mdfind: \(appURL.path, privacy: .public)") let config = NSWorkspace.OpenConfiguration() config.activates = true try await workspace.openApplication(at: appURL, configuration: config) + let focused = await verifyFrontmost(expectedName: resolvedName) + if requireExactMatch && !focused { + let frontmost = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + throw ExecutorError.focusAcquireFailed("Expected '\(resolvedName)' but frontmost is '\(frontmost)'") + } return } + log.error("openApp failed: app_not_found name=\(name, privacy: .public) resolvedName=\(resolvedName, privacy: .public) bundleId=\(bundleId ?? "nil", privacy: .public) attempted=[\(attemptedPaths.joined(separator: ", "), privacy: .public)]") throw ExecutorError.appNotFound(name) } diff --git a/clients/macos/vellum-assistant/ComputerUse/ActionTypes.swift b/clients/macos/vellum-assistant/ComputerUse/ActionTypes.swift index e92df88654e..b51066684f2 100644 --- a/clients/macos/vellum-assistant/ComputerUse/ActionTypes.swift +++ b/clients/macos/vellum-assistant/ComputerUse/ActionTypes.swift @@ -36,11 +36,14 @@ struct AgentAction: Codable { var summary: String? var waitDuration: Int? var appName: String? + var appBundleId: String? var script: String? var reasoning: String var resolvedFromElementId: Int? var resolvedToElementId: Int? var elementDescription: String? + /// When true, openApp must resolve via bundle ID or exact name only — no fuzzy matching. + var requireExactAppMatch: Bool init( type: ActionType, @@ -56,10 +59,12 @@ struct AgentAction: Codable { summary: String? = nil, waitDuration: Int? = nil, appName: String? = nil, + appBundleId: String? = nil, script: String? = nil, resolvedFromElementId: Int? = nil, resolvedToElementId: Int? = nil, - elementDescription: String? = nil + elementDescription: String? = nil, + requireExactAppMatch: Bool = false ) { self.type = type self.reasoning = reasoning @@ -74,10 +79,12 @@ struct AgentAction: Codable { self.summary = summary self.waitDuration = waitDuration self.appName = appName + self.appBundleId = appBundleId self.script = script self.resolvedFromElementId = resolvedFromElementId self.resolvedToElementId = resolvedToElementId self.elementDescription = elementDescription + self.requireExactAppMatch = requireExactAppMatch } var targetMode: ActionTargetMode { diff --git a/clients/macos/vellum-assistant/ComputerUse/FocusManager.swift b/clients/macos/vellum-assistant/ComputerUse/FocusManager.swift new file mode 100644 index 00000000000..8ed26446e71 --- /dev/null +++ b/clients/macos/vellum-assistant/ComputerUse/FocusManager.swift @@ -0,0 +1,225 @@ +import AppKit +import ApplicationServices +import os + +private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "FocusManager") + +/// Centralized focus acquisition with multi-strategy retry and AX-level window raise. +/// +/// Strategies (in order): +/// 1. Unhide the target app (handles NSApp.hide / isHidden state) +/// 2. NSRunningApplication.activate(options:) with .activateIgnoringOtherApps +/// 3. AX-level kAXRaiseAction on the main/focused window (bypasses WM quirks) +/// 4. Verify frontmost app matches target +/// +/// Each attempt includes a settle delay to let the window server process the switch. +@MainActor +final class FocusManager { + + /// Result of a focus acquisition attempt. + enum FocusResult { + case success + case targetNotRunning + case failed(reason: String) + } + + /// The settle delay after each activation attempt (in nanoseconds). + private static let settleDelayNs: UInt64 = 300_000_000 // 300ms + + /// Attempts to bring the target app to the foreground and verify it's frontmost. + /// + /// - Parameters: + /// - bundleId: Target app's bundle identifier (preferred, most reliable). + /// - appName: Target app's display name (fallback when no bundle ID). + /// - maxRetries: Maximum activation attempts (default 2 for strict, 1 for normal). + /// - Returns: `.success` if the target is frontmost, or a failure reason. + func acquireVerifiedFocus( + bundleId: String?, + appName: String?, + maxRetries: Int = 2 + ) async -> FocusResult { + let frontmostBefore = NSWorkspace.shared.frontmostApplication + log.info("Focus: acquiring target=\(bundleId ?? "nil", privacy: .public)/\(appName ?? "nil", privacy: .public) frontmostBefore=\(frontmostBefore?.localizedName ?? "nil", privacy: .public)(\(frontmostBefore?.bundleIdentifier ?? "nil", privacy: .public))") + + // Fast path: already frontmost + if isFrontmost(bundleId: bundleId, appName: appName) { + log.info("Focus: already frontmost — no activation needed") + return .success + } + + // Resolve the running application (deterministic: prefers visible-window instance) + guard let targetApp = resolveRunningApp(bundleId: bundleId, appName: appName) else { + log.warning("Focus: target not running bundleId=\(bundleId ?? "nil", privacy: .public) appName=\(appName ?? "nil", privacy: .public)") + return .targetNotRunning + } + + let displayName = targetApp.localizedName ?? appName ?? bundleId ?? "unknown" + let targetPID = targetApp.processIdentifier + log.info("Focus: resolved target=\(displayName, privacy: .public) pid=\(targetPID)") + + for attempt in 1...maxRetries { + // Step 1: Unhide if hidden + if targetApp.isHidden { + targetApp.unhide() + log.info("Focus[\(attempt)]: unhid \(displayName, privacy: .public)") + } + + // Special handling for our own app (LSUIElement / .accessory policy) + let selfBundleId = Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant" + if targetApp.bundleIdentifier == selfBundleId { + NSApp.unhide(nil) + for window in NSApp.windows where window.isMiniaturized { + window.deminiaturize(nil) + } + } + + // Step 2: NSRunningApplication.activate + targetApp.activate(options: [.activateIgnoringOtherApps, .activateAllWindows]) + + // Step 3: AX-level window raise — more reliable for stubborn windows + raiseMainWindowViaAX(pid: targetPID) + + // Settle delay + try? await Task.sleep(nanoseconds: Self.settleDelayNs) + + // Step 4: Verify + if isFrontmost(bundleId: bundleId, appName: appName) { + let frontmostAfter = NSWorkspace.shared.frontmostApplication + log.info("Focus[\(attempt)]: verified \(displayName, privacy: .public) is frontmost pid=\(targetPID) frontmostAfter=\(frontmostAfter?.localizedName ?? "nil", privacy: .public)") + return .success + } + + let currentFrontmost = NSWorkspace.shared.frontmostApplication + log.warning("Focus[\(attempt)/\(maxRetries)]: \(displayName, privacy: .public) not frontmost after activation — frontmost is \(currentFrontmost?.localizedName ?? "unknown", privacy: .public)(\(currentFrontmost?.bundleIdentifier ?? "nil", privacy: .public))") + } + + let frontmostName = NSWorkspace.shared.frontmostApplication?.localizedName ?? "unknown" + let frontmostBid = NSWorkspace.shared.frontmostApplication?.bundleIdentifier ?? "nil" + let reason = "Could not activate '\(displayName)' (pid=\(targetPID)) after \(maxRetries) attempts. Frontmost is '\(frontmostName)' (\(frontmostBid))." + log.error("Focus: FAILED — \(reason, privacy: .public)") + return .failed(reason: reason) + } + + // MARK: - Private Helpers + + /// Checks whether the frontmost app matches the target by bundle ID or name. + private func isFrontmost(bundleId: String?, appName: String?) -> Bool { + guard let frontmost = NSWorkspace.shared.frontmostApplication else { return false } + + if let bid = bundleId, !bid.isEmpty { + return frontmost.bundleIdentifier == bid + } + if let name = appName, !name.isEmpty { + return frontmost.localizedName == name + } + // No target constraint — always matches + return true + } + + /// Finds the NSRunningApplication for the target. + /// + /// When multiple instances match (e.g., two Chrome instances), prefers the one + /// whose PID owns a visible layer-0 window (determined via `CGWindowListCopyWindowInfo`). + /// This avoids picking a headless/background instance that can't be focused. + private func resolveRunningApp(bundleId: String?, appName: String?) -> NSRunningApplication? { + let workspace = NSWorkspace.shared + + let candidates: [NSRunningApplication] + if let bid = bundleId, !bid.isEmpty { + candidates = workspace.runningApplications.filter { $0.bundleIdentifier == bid } + } else if let name = appName, !name.isEmpty { + candidates = workspace.runningApplications.filter { $0.localizedName == name } + } else { + return nil + } + + guard !candidates.isEmpty else { return nil } + if candidates.count == 1 { return candidates.first } + + // Multiple instances — prefer the one with a visible layer-0 window + let visiblePIDs = Self.pidsWithVisibleWindows() + log.info("Focus: \(candidates.count) candidate instances, visiblePIDs=\(visiblePIDs)") + + for candidate in candidates { + if visiblePIDs.contains(candidate.processIdentifier) { + log.info("Focus: chose pid=\(candidate.processIdentifier) (has visible window)") + return candidate + } + } + + // No candidate has a visible window — fall back to first + log.info("Focus: no candidate has visible window, using first (pid=\(candidates[0].processIdentifier))") + return candidates.first + } + + /// Returns PIDs that own at least one visible, layer-0 (normal) window. + private static func pidsWithVisibleWindows() -> Set { + guard let windowList = CGWindowListCopyWindowInfo( + [.optionOnScreenOnly, .excludeDesktopElements], + kCGNullWindowID + ) as? [[String: Any]] else { + return [] + } + + var pids = Set() + for info in windowList { + guard let pid = info[kCGWindowOwnerPID as String] as? pid_t, + let layer = info[kCGWindowLayer as String] as? Int, layer == 0 else { + continue + } + pids.insert(pid) + } + return pids + } + + /// Uses AX APIs to raise the main/focused window of the target app. + /// + /// This is more reliable than NSRunningApplication.activate() for apps that + /// have multiple windows or that the WM doesn't bring to front on activate alone. + /// + /// Sequence: + /// 1. Get focused or main window + /// 2. Set kAXMainAttribute on the window (make it the main window) + /// 3. Perform kAXRaiseAction (bring window to front) + /// 4. Set kAXFocusedAttribute on the app element (transfer keyboard focus) + private func raiseMainWindowViaAX(pid: pid_t) { + let appElement = AXUIElementCreateApplication(pid) + AXUIElementSetMessagingTimeout(appElement, 3.0) + + // Try focused window first, fall back to main window + var windowRef: CFTypeRef? + var result = AXUIElementCopyAttributeValue(appElement, kAXFocusedWindowAttribute as CFString, &windowRef) + if result != .success { + result = AXUIElementCopyAttributeValue(appElement, kAXMainWindowAttribute as CFString, &windowRef) + } + + guard result == .success, + let windowValue = windowRef, + CFGetTypeID(windowValue) == AXUIElementGetTypeID() else { + log.debug("AX raise: no focused/main window for pid \(pid)") + return + } + + let window = windowValue as! AXUIElement + + // Set as main window + let mainResult = AXUIElementSetAttributeValue(window, kAXMainAttribute as CFString, kCFBooleanTrue) + if mainResult != .success { + log.debug("AX raise: kAXMainAttribute set failed for pid \(pid): \(mainResult.rawValue)") + } + + // Raise the window to front + let raiseResult = AXUIElementPerformAction(window, kAXRaiseAction as CFString) + if raiseResult == .success { + log.debug("AX raise: raised window for pid \(pid)") + } else { + log.debug("AX raise: kAXRaiseAction failed for pid \(pid): \(raiseResult.rawValue)") + } + + // Set focused attribute on the app element to transfer keyboard focus + let focusResult = AXUIElementSetAttributeValue(appElement, kAXFocusedAttribute as CFString, kCFBooleanTrue) + if focusResult != .success { + log.debug("AX raise: kAXFocusedAttribute set failed for pid \(pid): \(focusResult.rawValue)") + } + } +} diff --git a/clients/macos/vellum-assistant/ComputerUse/ScreenRecorder.swift b/clients/macos/vellum-assistant/ComputerUse/ScreenRecorder.swift new file mode 100644 index 00000000000..6b646bbb9af --- /dev/null +++ b/clients/macos/vellum-assistant/ComputerUse/ScreenRecorder.swift @@ -0,0 +1,444 @@ +import Foundation +import ScreenCaptureKit +import AVFoundation +import CoreMedia +import os + +private let log = Logger(subsystem: "com.vellum.vellum-assistant", category: "ScreenRecorder") + +// MARK: - Recording Result + +/// Metadata returned after a screen recording session completes. +struct RecordingResult: Sendable { + let fileURL: URL + let mimeType: String // always "video/mp4" + let sizeBytes: Int + let durationMs: Int + let width: Int + let height: Int + let captureScope: String // "window" or "display" + let includeAudio: Bool + let targetBundleId: String? +} + +// MARK: - Recording Errors + +enum ScreenRecorderError: LocalizedError { + case alreadyRecording + case notRecording + case permissionDenied + case noDisplayFound + case windowNotFound(CGWindowID) + case assetWriterSetupFailed(String) + case assetWriterFailed(String) + case recordingDirectoryCreationFailed + + var errorDescription: String? { + switch self { + case .alreadyRecording: + return "Screen recording is already in progress" + case .notRecording: + return "No active screen recording to stop" + case .permissionDenied: + return "Screen Recording permission denied. Grant it in System Settings > Privacy & Security > Screen Recording." + case .noDisplayFound: + return "No display found for recording" + case .windowNotFound(let id): + return "Window with ID \(id) not found for recording" + case .assetWriterSetupFailed(let reason): + return "Failed to set up recording writer: \(reason)" + case .assetWriterFailed(let reason): + return "Recording writer error: \(reason)" + case .recordingDirectoryCreationFailed: + return "Failed to create recordings directory" + } + } +} + +// MARK: - Protocol + +/// Protocol for screen recording, enabling dependency injection and testing. +@MainActor +protocol ScreenRecording { + func startRecording(windowID: CGWindowID?, displayID: CGDirectDisplayID?, includeAudio: Bool) async throws + func stopRecording() async throws -> RecordingResult + func waitForFirstFrame(timeoutSeconds: Double) async -> Bool + var isRecording: Bool { get } +} + +extension ScreenRecording { + func waitForFirstFrame(timeoutSeconds: Double = 5.0) async -> Bool { + return true // Mocks assume healthy capture by default + } +} + +// MARK: - ScreenRecorder + +/// Records screen content to an .mp4 file using ScreenCaptureKit (SCStream). +/// +/// Supports two capture scopes: +/// - **Window capture**: captures a specific window by CGWindowID +/// - **Display capture**: captures the full display (fallback) +/// +/// Recordings are saved to `~/Library/Application Support/vellum-assistant/recordings/`. +@MainActor +final class ScreenRecorder: NSObject, ScreenRecording { + private(set) var isRecording = false + /// The file URL of the last recording attempt, available even after failure for salvage. + private(set) var lastRecordingFileURL: URL? + + private var stream: SCStream? + private var assetWriter: AVAssetWriter? + private var videoInput: AVAssetWriterInput? + private var audioInput: AVAssetWriterInput? + private var recordingFileURL: URL? + private var recordingStartTime: Date? + private var captureScope: String = "display" + private var includesAudio: Bool = false + private var targetBundleId: String? + private var captureWidth: Int = 0 + private var captureHeight: Int = 0 + + /// Nonisolated delegate that buffers samples and forwards them to the asset writer. + /// Must be nonisolated because SCStreamOutput callbacks arrive on an arbitrary queue. + private var outputHandler: StreamOutputHandler? + + // MARK: - Directory Setup + + private static func recordingsDirectory() throws -> URL { + let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! + let recordingsDir = appSupport + .appendingPathComponent("vellum-assistant", isDirectory: true) + .appendingPathComponent("recordings", isDirectory: true) + + if !FileManager.default.fileExists(atPath: recordingsDir.path) { + do { + try FileManager.default.createDirectory(at: recordingsDir, withIntermediateDirectories: true) + } catch { + log.error("Failed to create recordings directory: \(error.localizedDescription)") + throw ScreenRecorderError.recordingDirectoryCreationFailed + } + } + + return recordingsDir + } + + // MARK: - Start Recording + + func startRecording(windowID: CGWindowID? = nil, displayID: CGDirectDisplayID? = nil, includeAudio: Bool = false) async throws { + guard !isRecording else { + throw ScreenRecorderError.alreadyRecording + } + + // Fetch shareable content (triggers permission prompt if needed) + let content: SCShareableContent + do { + content = try await SCShareableContent.current + } catch { + throw ScreenRecorderError.permissionDenied + } + + // Build content filter based on capture scope + let filter: SCContentFilter + if let windowID, let window = content.windows.first(where: { $0.windowID == windowID }) { + filter = SCContentFilter(desktopIndependentWindow: window) + captureScope = "window" + targetBundleId = window.owningApplication?.bundleIdentifier + log.info("Recording window \(windowID) (bundle: \(self.targetBundleId ?? "unknown"))") + } else if let displayID, let display = content.displays.first(where: { $0.displayID == displayID }) { + // Exclude our own app's windows from display capture + let myPID = ProcessInfo.processInfo.processIdentifier + let ownWindows = content.windows.filter { $0.owningApplication?.processID == myPID } + filter = SCContentFilter(display: display, excludingWindows: ownWindows) + captureScope = "display" + targetBundleId = nil + log.info("Recording display \(displayID)") + } else { + // Fallback: use main display + let mainDisplayID = CGMainDisplayID() + guard let display = content.displays.first(where: { $0.displayID == mainDisplayID }) + ?? content.displays.first else { + throw ScreenRecorderError.noDisplayFound + } + let myPID = ProcessInfo.processInfo.processIdentifier + let ownWindows = content.windows.filter { $0.owningApplication?.processID == myPID } + filter = SCContentFilter(display: display, excludingWindows: ownWindows) + captureScope = "display" + targetBundleId = nil + log.info("Recording main display (fallback)") + } + + includesAudio = includeAudio + + // Configure the stream + let config = SCStreamConfiguration() + config.width = 1920 + config.height = 1080 + config.pixelFormat = kCVPixelFormatType_32BGRA + config.showsCursor = true + config.minimumFrameInterval = CMTime(value: 1, timescale: 30) // 30 fps + + if includeAudio { + config.capturesAudio = true + config.sampleRate = 44100 + config.channelCount = 2 + } + + captureWidth = config.width + captureHeight = config.height + + // Set up the output file + let recordingsDir = try Self.recordingsDirectory() + let timestamp = ISO8601DateFormatter().string(from: Date()) + .replacingOccurrences(of: ":", with: "-") + let fileName = "qa-recording-\(timestamp).mp4" + let fileURL = recordingsDir.appendingPathComponent(fileName) + recordingFileURL = fileURL + lastRecordingFileURL = fileURL + + // Set up AVAssetWriter + let writer: AVAssetWriter + do { + writer = try AVAssetWriter(outputURL: fileURL, fileType: .mp4) + } catch { + throw ScreenRecorderError.assetWriterSetupFailed(error.localizedDescription) + } + + // Video input + let videoSettings: [String: Any] = [ + AVVideoCodecKey: AVVideoCodecType.h264, + AVVideoWidthKey: config.width, + AVVideoHeightKey: config.height, + AVVideoCompressionPropertiesKey: [ + AVVideoAverageBitRateKey: 4_000_000, // 4 Mbps + AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel, + ] + ] + let vInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings) + vInput.expectsMediaDataInRealTime = true + guard writer.canAdd(vInput) else { + throw ScreenRecorderError.assetWriterSetupFailed("Cannot add video input to asset writer") + } + writer.add(vInput) + videoInput = vInput + + // Audio input (optional) + if includeAudio { + let audioSettings: [String: Any] = [ + AVFormatIDKey: kAudioFormatMPEG4AAC, + AVSampleRateKey: 44100, + AVNumberOfChannelsKey: 2, + AVEncoderBitRateKey: 128_000, + ] + let aInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings) + aInput.expectsMediaDataInRealTime = true + if writer.canAdd(aInput) { + writer.add(aInput) + audioInput = aInput + } + } + + writer.startWriting() + assetWriter = writer + + // Create the nonisolated output handler + let handler = StreamOutputHandler(writer: writer, videoInput: vInput, audioInput: audioInput) + outputHandler = handler + + // Create and start the stream + let scStream = SCStream(filter: filter, configuration: config, delegate: nil) + try scStream.addStreamOutput(handler, type: .screen, sampleHandlerQueue: .global(qos: .userInitiated)) + if includeAudio { + try scStream.addStreamOutput(handler, type: .audio, sampleHandlerQueue: .global(qos: .userInitiated)) + } + + try await scStream.startCapture() + stream = scStream + isRecording = true + recordingStartTime = Date() + + log.info("Screen recording started: \(fileURL.lastPathComponent)") + } + + // MARK: - First Frame Handshake + + /// Waits for the first video frame to arrive, returning true if received within the timeout. + func waitForFirstFrame(timeoutSeconds: Double = 5.0) async -> Bool { + guard let handler = outputHandler else { return false } + + return await withTaskGroup(of: Bool.self) { group in + group.addTask { + await withCheckedContinuation { continuation in + handler.setFirstFrameContinuation(continuation) + } + return true + } + group.addTask { + try? await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000)) + handler.cancelFirstFrameWait() + return false + } + let result = await group.next() ?? false + group.cancelAll() + return result + } + } + + // MARK: - Stop Recording + + func stopRecording() async throws -> RecordingResult { + guard isRecording, let stream, let writer = assetWriter, let fileURL = recordingFileURL else { + throw ScreenRecorderError.notRecording + } + + // Capture values needed for result computation before cleanup + let capturedStream = stream + let capturedWriter = writer + let capturedFileURL = fileURL + let capturedStartTime = recordingStartTime + let capturedVideoInput = videoInput + let capturedAudioInput = audioInput + let capturedWidth = captureWidth + let capturedHeight = captureHeight + let capturedScope = captureScope + let capturedIncludesAudio = includesAudio + let capturedTargetBundleId = targetBundleId + + // Guarantee state cleanup on all paths (including throws) + defer { + self.stream = nil + self.assetWriter = nil + self.videoInput = nil + self.audioInput = nil + self.outputHandler = nil + self.recordingFileURL = nil + self.recordingStartTime = nil + self.isRecording = false + } + + // Stop the stream capture + do { + try await capturedStream.stopCapture() + } catch { + log.warning("Error stopping stream capture: \(error.localizedDescription)") + } + + // Mark inputs as finished + capturedVideoInput?.markAsFinished() + capturedAudioInput?.markAsFinished() + + // Finalize the asset writer + await capturedWriter.finishWriting() + + if capturedWriter.status == .failed { + let writerError = capturedWriter.error + let nsError = writerError as? NSError + let errorMsg = writerError?.localizedDescription ?? "Unknown error" + let domain = nsError?.domain ?? "unknown" + let code = nsError?.code ?? -1 + log.error("Asset writer failed: \(errorMsg) (domain=\(domain), code=\(code))") + throw ScreenRecorderError.assetWriterFailed(errorMsg) + } + + // Compute metadata + let fileAttributes = try FileManager.default.attributesOfItem(atPath: capturedFileURL.path) + let sizeBytes = (fileAttributes[.size] as? Int) ?? 0 + + // Compute duration from the asset + let asset = AVAsset(url: capturedFileURL) + let duration: CMTime + if let tracks = try? await asset.load(.tracks), !tracks.isEmpty { + duration = try await asset.load(.duration) + } else { + // Fallback: estimate from wall clock time + let elapsed = capturedStartTime.map { Date().timeIntervalSince($0) } ?? 0 + duration = CMTime(seconds: elapsed, preferredTimescale: 1000) + } + let durationMs = Int(CMTimeGetSeconds(duration) * 1000) + + let result = RecordingResult( + fileURL: capturedFileURL, + mimeType: "video/mp4", + sizeBytes: sizeBytes, + durationMs: durationMs, + width: capturedWidth, + height: capturedHeight, + captureScope: capturedScope, + includeAudio: capturedIncludesAudio, + targetBundleId: capturedTargetBundleId + ) + + log.info("Screen recording stopped: \(capturedFileURL.lastPathComponent) (\(sizeBytes) bytes, \(durationMs)ms)") + + return result + } +} + +// MARK: - Stream Output Handler + +/// Nonisolated handler for SCStream output that writes samples to an AVAssetWriter. +/// SCStreamOutput callbacks arrive on arbitrary queues, so this class must not be +/// @MainActor-isolated. +private final class StreamOutputHandler: NSObject, SCStreamOutput, @unchecked Sendable { + private let writer: AVAssetWriter + private let videoInput: AVAssetWriterInput + private let audioInput: AVAssetWriterInput? + private var sessionStarted = false + private let lock = NSLock() + private var firstFrameContinuation: CheckedContinuation? + private let firstFrameLock = NSLock() + + init(writer: AVAssetWriter, videoInput: AVAssetWriterInput, audioInput: AVAssetWriterInput?) { + self.writer = writer + self.videoInput = videoInput + self.audioInput = audioInput + super.init() + } + + func setFirstFrameContinuation(_ continuation: CheckedContinuation?) { + firstFrameLock.lock() + defer { firstFrameLock.unlock() } + firstFrameContinuation = continuation + } + + func cancelFirstFrameWait() { + firstFrameLock.lock() + defer { firstFrameLock.unlock() } + firstFrameContinuation = nil + } + + func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { + guard writer.status == .writing else { return } + guard sampleBuffer.isValid else { return } + + lock.lock() + defer { lock.unlock() } + + // Start the session on the first sample + if !sessionStarted { + let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + writer.startSession(atSourceTime: timestamp) + sessionStarted = true + + // Signal that the first frame has been received + firstFrameLock.lock() + let cont = firstFrameContinuation + firstFrameContinuation = nil + firstFrameLock.unlock() + cont?.resume() + } + + switch type { + case .screen: + if videoInput.isReadyForMoreMediaData { + videoInput.append(sampleBuffer) + } + case .audio: + if let audioInput, audioInput.isReadyForMoreMediaData { + audioInput.append(sampleBuffer) + } + @unknown default: + break + } + } +} diff --git a/clients/macos/vellum-assistant/ComputerUse/Session.swift b/clients/macos/vellum-assistant/ComputerUse/Session.swift index e21dfc64743..59caf0e5ea7 100644 --- a/clients/macos/vellum-assistant/ComputerUse/Session.swift +++ b/clients/macos/vellum-assistant/ComputerUse/Session.swift @@ -2,6 +2,8 @@ import Foundation import VellumAssistantShared import CoreGraphics import AppKit +import AVFoundation +import CoreMedia import os private let log = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant", category: "Session") @@ -18,11 +20,45 @@ enum SessionState: Equatable { case cancelled } +struct PendingToolPermissionPrompt: Equatable { + let requestId: String + let toolName: String + let riskLevel: String + let summary: String +} + @MainActor final class ComputerUseSession: ObservableObject { @Published var state: SessionState = .idle @Published var undoCount = 0 - @Published var autoApproveTools = false + @Published var autoApproveTools = false { + didSet { + guard autoApproveTools != oldValue else { return } + log.info("Auto-approve tools toggled: \(self.autoApproveTools)") + + // Notify daemon so it can skip prompt creation proactively + do { + try daemonClient.send(CuAutoApproveUpdateMessage(sessionId: id, enabled: autoApproveTools)) + } catch { + log.error("Failed to send cu_auto_approve_update: \(error)") + } + + // Retro-resolve: if toggling ON and a low/medium-risk prompt is pending, approve it immediately + if autoApproveTools, let pending = pendingToolPermissionPrompt { + let normalizedRisk = pending.riskLevel.lowercased() + if normalizedRisk == "low" || normalizedRisk == "medium" { + log.info("Retro-resolving pending prompt \(pending.requestId) (tool=\(pending.toolName), risk=\(normalizedRisk))") + do { + try daemonClient.send(ConfirmationResponseMessage(requestId: pending.requestId, decision: "allow")) + pendingToolPermissionPrompt = nil + } catch { + log.error("Failed to retro-resolve pending prompt: \(error) — keeping prompt visible for manual approval") + } + } + } + } + } + @Published var pendingToolPermissionPrompt: PendingToolPermissionPrompt? let task: String let id: String @@ -34,6 +70,28 @@ final class ComputerUseSession: ObservableObject { private let skipSessionCreate: Bool private let notificationService: ActivityNotificationServiceProtocol? + /// Screen recorder — nil when recording is not requested (neither QA nor standalone recording). + private let screenRecorder: ScreenRecording? + /// Origin chat session ID for result injection (QA workflow). + let reportToSessionId: String? + /// Whether this session is running in QA/test mode. + let qaMode: Bool + /// Recording retention in days (from daemon config, default 7). + let retentionDays: Int + /// Capture scope for screen recording (from daemon config, default "display"). + let captureScope: String + /// Whether to include audio in screen recording (from daemon config, default false). + let includeAudio: Bool + /// When true, recording MUST start before any action; failure aborts the session. + let requiresRecording: Bool + + /// Target app name for frontmost-app guard — nil means no constraint. + let targetAppName: String? + /// Target app bundle ID for frontmost-app guard — nil means no constraint. + let targetAppBundleId: String? + /// When true, enforces that the target app must be visually frontmost during the session. + let strictVisualQa: Bool + /// Weak reference to the chat view model for extracting tool calls for notifications. weak var relatedViewModel: ChatViewModel? @@ -41,6 +99,7 @@ final class ComputerUseSession: ObservableObject { private var isPaused = false private var confirmationContinuation: CheckedContinuation? private var messageLoopTask: Task? + private var cancelSafetyNetTask: Task? private let enumerator: AccessibilityTreeProviding private let screenCapture: ScreenCaptureProviding @@ -48,12 +107,24 @@ final class ComputerUseSession: ObservableObject { private let verifier: ActionVerifier private let logger: SessionLogger private let initialDelayMs: UInt64 + private let focusManager = FocusManager() + private lazy var axActionExecutor: AXActionExecutor? = { + guard let registry = enumerator.elementRegistry else { return nil } + return AXActionExecutor(elementRegistry: registry) + }() private var didChromeAccessibilityCheck = false private var previousAXTreeText: String? private var previousElements: [AXElement]? private var previousFlatElements: [AXElement]? private var consecutiveUnchangedSteps = 0 private var currentStepNumber = 0 + private var consecutiveFrontmostBlocks = 0 + private var didFinalizeRecording = false + /// Whether this session should finalize recording on completion (has a recorder, regardless of QA mode). + private var shouldFinalizeRecording: Bool { screenRecorder != nil } + @Published private(set) var qaRecordingWarningMessage: String? + /// Whether screen recording is currently active (for UI indicator). + @Published private(set) var isRecordingActive: Bool = false /// Adaptive delay configuration private let adaptiveDelayEnabled: Bool @@ -64,7 +135,7 @@ final class ComputerUseSession: ObservableObject { init( task: String, daemonClient: DaemonClientProtocol, - enumerator: AccessibilityTreeProviding = AccessibilityTreeEnumerator(), + enumerator: AccessibilityTreeProviding? = nil, screenCapture: ScreenCaptureProviding = ScreenCapture(), executor: ActionExecuting = ActionExecutor(), maxSteps: Int = 50, @@ -74,14 +145,45 @@ final class ComputerUseSession: ObservableObject { adaptiveDelay: Bool = true, sessionId: String? = nil, skipSessionCreate: Bool = false, - notificationService: ActivityNotificationServiceProtocol? = nil + notificationService: ActivityNotificationServiceProtocol? = nil, + screenRecorder: ScreenRecording? = nil, + reportToSessionId: String? = nil, + qaMode: Bool = false, + retentionDays: Int = 7, + captureScope: String = "display", + includeAudio: Bool = false, + requiresRecording: Bool = false, + targetAppName: String? = nil, + targetAppBundleId: String? = nil, + strictVisualQa: Bool = false ) { self.id = sessionId ?? UUID().uuidString self.task = task self.attachments = attachments self.daemonClient = daemonClient self.interactionType = interactionType - self.enumerator = enumerator + + // Auto-configure enumerator: when the session targets Vellum itself, + // allow enumerating our own window instead of skipping to the previous app. + if let enumerator = enumerator { + self.enumerator = enumerator + } else { + let selfBundleId = Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant" + let isSelfTargeted: Bool + if let bid = targetAppBundleId, !bid.isEmpty { + isSelfTargeted = bid == selfBundleId + } else if let name = targetAppName, !name.isEmpty { + let lower = name.lowercased() + isSelfTargeted = lower == "vellum" || lower == "vellum assistant" + } else { + isSelfTargeted = false + } + let newEnumerator = AccessibilityTreeEnumerator() + if isSelfTargeted { + newEnumerator.allowSelfEnumeration = true + } + self.enumerator = newEnumerator + } self.screenCapture = screenCapture self.executor = executor self.maxSteps = maxSteps @@ -89,6 +191,16 @@ final class ComputerUseSession: ObservableObject { self.adaptiveDelayEnabled = adaptiveDelay self.skipSessionCreate = skipSessionCreate self.notificationService = notificationService + self.screenRecorder = screenRecorder + self.reportToSessionId = reportToSessionId + self.qaMode = qaMode + self.retentionDays = retentionDays + self.captureScope = captureScope + self.includeAudio = includeAudio + self.requiresRecording = requiresRecording + self.targetAppName = targetAppName + self.targetAppBundleId = targetAppBundleId + self.strictVisualQa = strictVisualQa self.verifier = ActionVerifier(maxSteps: maxSteps) self.logger = SessionLogger(task: task, attachments: attachments) } @@ -97,13 +209,24 @@ final class ComputerUseSession: ObservableObject { verifier.reset() isCancelled = false isPaused = false + pendingToolPermissionPrompt = nil + qaRecordingWarningMessage = nil + isRecordingActive = false previousAXTreeText = nil previousElements = nil previousFlatElements = nil consecutiveUnchangedSteps = 0 currentStepNumber = 0 + consecutiveFrontmostBlocks = 0 + didFinalizeRecording = false state = .running(step: 0, maxSteps: maxSteps, lastAction: "Starting...", reasoning: "") + // QA sessions auto-approve low/medium tools from the start. + // Set here (not in init) so the didSet fires and notifies the daemon. + if qaMode && !autoApproveTools { + autoApproveTools = true + } + log.info("Session starting — task: \(self.task, privacy: .public)") let screenSize = screenCapture.screenSize() @@ -114,6 +237,81 @@ final class ComputerUseSession: ObservableObject { try? await Task.sleep(nanoseconds: initialDelayMs * 1_000_000) } + // Start screen recording when a recorder is available (QA or standalone recording) + if let recorder = screenRecorder { + do { + var windowID: CGWindowID? + var displayID: CGDirectDisplayID? + if captureScope == "window" { + // Resolve a target window's CGWindowID for window-scoped capture. + // Exclude our own PID so we never accidentally record Vellum's + // window or overlay (which may still be frontmost in direct-start flow). + let myPID = ProcessInfo.processInfo.processIdentifier + if let windowList = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] { + for info in windowList { + if let ownerPID = info[kCGWindowOwnerPID as String] as? pid_t, + ownerPID != myPID, + let layer = info[kCGWindowLayer as String] as? Int, layer == 0, + let wid = info[kCGWindowNumber as String] as? CGWindowID { + windowID = wid + break + } + } + } + if windowID == nil { + log.warning("Recording: captureScope is 'window' but no suitable target window found — falling back to display capture") + } + } else { + displayID = CGMainDisplayID() + } + try await recorder.startRecording(windowID: windowID, displayID: displayID, includeAudio: self.includeAudio) + + // Verify capture pipeline is healthy by waiting for first frame + let firstFrameReceived = await recorder.waitForFirstFrame(timeoutSeconds: 5.0) + if !firstFrameReceived { + log.error("Recording: first video frame not received within 5 seconds — capture pipeline unhealthy") + try? daemonClient.send(CuRecordingStatusMessage(sessionId: id, status: "failed", reason: "First frame not received within 5 seconds")) + if requiresRecording { + state = .failed(reason: "Recording required but capture pipeline failed: no frames received") + qaRecordingWarningMessage = "Recording required but no video frames received" + logger.finishSession(result: "failed: first frame timeout") + cancelSafetyNetTask?.cancel() + cancelSafetyNetTask = nil + await finalizeRecording() + return + } + qaRecordingWarningMessage = "Recording may be incomplete: capture pipeline was slow to start" + } + + log.info("Recording: screen recording started for session \(self.id) (scope: \(self.captureScope))") + isRecordingActive = true + // Notify daemon that recording started successfully + try? daemonClient.send(CuRecordingStatusMessage(sessionId: id, status: "started")) + } catch { + let reason: String + if let recorderError = error as? ScreenRecorderError { + reason = recorderError.errorDescription ?? error.localizedDescription + } else { + reason = error.localizedDescription + } + log.error("Recording: failed to start screen recording: \(reason)") + // Notify daemon of recording failure + try? daemonClient.send(CuRecordingStatusMessage(sessionId: id, status: "failed", reason: reason)) + if requiresRecording { + // Fail-closed: recording is mandatory — abort the session + state = .failed(reason: "Recording required but failed to start: \(reason)") + qaRecordingWarningMessage = "Recording required but failed: \(reason)" + logger.finishSession(result: "failed: required recording could not start") + cancelSafetyNetTask?.cancel() + cancelSafetyNetTask = nil + await finalizeRecording() + return + } + // Non-fatal for best-effort recording — continue the session without recording + qaRecordingWarningMessage = "Unable to start recording. \(reason)" + } + } + // 1. Subscribe before sending so we don't miss fast daemon responses let messageStream = daemonClient.subscribe() @@ -138,10 +336,24 @@ final class ComputerUseSession: ObservableObject { screenWidth: Int(screenSize.width), screenHeight: Int(screenSize.height), attachments: ipcAttachments, - interactionType: interactionTypeString + interactionType: interactionTypeString, + reportToSessionId: reportToSessionId, + qaMode: qaMode ? true : nil, + targetAppName: targetAppName, + targetAppBundleId: targetAppBundleId, + requiresRecording: requiresRecording ? true : nil )) } catch { log.error("Failed to send session create message: \(error)") + state = .failed(reason: "Assistant connection lost before session start") + logger.finishSession(result: "failed: session_create send failed") + // Disarm cancel safety net — this run is terminating early. + cancelSafetyNetTask?.cancel() + cancelSafetyNetTask = nil + if shouldFinalizeRecording { + await finalizeRecording() + } + return } } @@ -152,15 +364,35 @@ final class ComputerUseSession: ObservableObject { try daemonClient.send(obs) } catch { log.error("Failed to send initial observation: \(error)") + state = .failed(reason: "Assistant connection lost before first action") + logger.finishSession(result: "failed: initial observation send failed") + // Disarm cancel safety net — this run is terminating early. + cancelSafetyNetTask?.cancel() + cancelSafetyNetTask = nil + if shouldFinalizeRecording { + await finalizeRecording() + } + return } } else { state = .failed(reason: "No focused window and screen capture failed") + logger.finishSession(result: "failed: no window") + // Disarm the cancel safety net — run() reached the post-loop and will + // handle finalization + abort itself. + cancelSafetyNetTask?.cancel() + cancelSafetyNetTask = nil + + // Finalize recording BEFORE sending abort — the daemon's handleCuSessionAbort + // deletes cuSessionMetadata, so cu_session_finalized must arrive first for + // summary injection to work. + if shouldFinalizeRecording { + await finalizeRecording() + } do { try daemonClient.send(CuSessionAbortMessage(sessionId: id)) } catch { log.error("Failed to send session abort message: \(error)") } - logger.finishSession(result: "failed: no window") return } @@ -243,6 +475,28 @@ final class ComputerUseSession: ObservableObject { logger.finishSession(result: "failed: stream ended unexpectedly") } } + + // Disarm the cancel safety net — run() reached the post-loop and will + // handle finalization + abort itself. + cancelSafetyNetTask?.cancel() + cancelSafetyNetTask = nil + + // Finalize recording and send cu_session_finalized + // Guard: skip if already finalized (e.g. frontmost guard limit triggered finalization early) + if shouldFinalizeRecording && !didFinalizeRecording { + await finalizeRecording() + + // Send the abort immediately after finalization for cancelled QA sessions. + // This arrives before cancel()'s 2-second safety-net abort fires. + // The daemon deduplicates aborts, so the safety net is harmless if we get here. + if isCancelled { + do { + try daemonClient.send(CuSessionAbortMessage(sessionId: id)) + } catch { + log.error("Failed to send deferred session abort after QA finalization: \(error)") + } + } + } } // MARK: - Action Handler @@ -252,6 +506,8 @@ final class ComputerUseSession: ObservableObject { currentStepNumber = action.stepNumber guard let resolved = await resolveCoordinatesIfNeeded(for: agentAction, stepNumber: action.stepNumber) else { + // Coordinate resolution failures are not frontmost-guard blocks — reset the consecutive streak + consecutiveFrontmostBlocks = 0 return } agentAction = resolved @@ -277,6 +533,7 @@ final class ComputerUseSession: ObservableObject { // Handle done/respond completion actions — don't execute, wait for cu_complete if agentAction.type == .done || agentAction.type == .respond { + consecutiveFrontmostBlocks = 0 return } @@ -323,6 +580,8 @@ final class ComputerUseSession: ObservableObject { case .blocked(let reason): log.warning("[\(action.stepNumber)] BLOCKED: \(reason)") + // Verifier blocks are not frontmost-guard blocks — reset the consecutive streak + consecutiveFrontmostBlocks = 0 verifier.recordBlock() if verifier.consecutiveBlockCount >= 3 { isCancelled = true @@ -343,20 +602,166 @@ final class ComputerUseSession: ObservableObject { return } - // EXECUTE + // FOCUS-LOCK KEY BLOCKLIST — in strict visual QA mode, block key combos that + // would steal focus from the target app (hide, minimize, app-switch). + if strictVisualQa, agentAction.type == .key, let keyCombo = agentAction.key?.lowercased() { + let focusStealingKeys: Set = ["cmd+h", "command+h", "cmd+m", "command+m", + "cmd+tab", "command+tab", "cmd+`", "command+`"] + if focusStealingKeys.contains(keyCombo) { + let blockMsg = "Blocked in focus-lock mode: '\(keyCombo)' would steal focus from the target app." + log.warning("[\(action.stepNumber)] Focus-lock key block: \(keyCombo)") + let obs = await buildObservation(executionResult: nil, executionError: blockMsg) + if let obs { + do { try daemonClient.send(obs) } catch { + log.error("Failed to send focus-lock key block observation: \(error)") + } + } + state = .thinking(step: action.stepNumber + 1, maxSteps: maxSteps) + return + } + } + + // FRONTMOST-APP GUARD — block destructive actions when the wrong app is in front. + // Non-destructive actions (screenshot, open_app, wait, runAppleScript) are allowed + // regardless of which app is focused. + if let guardError = await checkFrontmostAppGuard(for: agentAction) { + consecutiveFrontmostBlocks += 1 + log.error("Frontmost guard BLOCKED action \(agentAction.type.rawValue) (\(self.consecutiveFrontmostBlocks) consecutive): \(guardError)") + + // In strict mode, fail immediately — the guard already retried activation + // internally, so a returned error means focus is unrecoverable. + // In normal mode, allow up to 3 cross-step blocks before failing. + let shouldFail = strictVisualQa || consecutiveFrontmostBlocks >= 3 + if shouldFail { + isCancelled = true + state = .failed(reason: strictVisualQa + ? "Focus-lock failed: \(guardError)" + : "Target app could not be activated after repeated attempts.") + logger.finishSession(result: "failed: frontmost guard\(strictVisualQa ? " (strict)" : "") — \(guardError)") + + // Finalize recording BEFORE sending abort — the daemon's handleCuSessionAbort + // deletes cuSessionMetadata, so cu_session_finalized must arrive first. + if shouldFinalizeRecording { + await finalizeRecording() + } + do { + try daemonClient.send(CuSessionAbortMessage(sessionId: id)) + } catch { + log.error("Failed to send session abort after frontmost guard limit: \(error)") + } + return + } + let obs = await buildObservation(executionResult: nil, executionError: guardError) + if let obs { + do { + try daemonClient.send(obs) + } catch { + log.error("Failed to send frontmost-guard blocked observation: \(error)") + } + } + state = .thinking(step: action.stepNumber + 1, maxSteps: maxSteps) + return + } + consecutiveFrontmostBlocks = 0 + + // EXECUTE — try AX-first for element-targeted actions, fall back to CGEvent var executionResult: String? = nil var executionError: String? = nil - do { - executionResult = try await executor.execute(agentAction) - } catch { - let errorMessage = error.localizedDescription - // AppleScript errors are non-fatal — let the daemon adapt - if agentAction.type == .runAppleScript { - log.warning("[\(action.stepNumber)] AppleScript error (non-fatal): \(errorMessage)") - executionError = errorMessage - } else { - executionError = errorMessage + var usedAXPath = false + if let axExecutor = axActionExecutor, let elementId = agentAction.resolvedFromElementId { + let axResult: AXActionResult + switch agentAction.type { + case .click, .doubleClick, .rightClick: + axResult = axExecutor.click(elementId: elementId) + case .type: + if let text = agentAction.text { + axResult = axExecutor.type(elementId: elementId, text: text) + } else { + axResult = .fallback(reason: "No text provided") + } + default: + axResult = .fallback(reason: "Action type \(agentAction.type.rawValue) not supported via AX") + } + + switch axResult { + case .success(let result): + executionResult = result + usedAXPath = true + log.info("[\(action.stepNumber)] AX-first action succeeded for [\(elementId)]") + case .fallback(let reason): + log.info("[\(action.stepNumber)] AX-first fallback: \(reason, privacy: .public)") + // Fall through to CGEvent execution below + } + } + + if !usedAXPath { + do { + executionResult = try await executor.execute(agentAction) + } catch { + let errorMessage = error.localizedDescription + + // AppleScript errors are non-fatal — let the daemon adapt + if agentAction.type == .runAppleScript { + log.warning("[\(action.stepNumber)] AppleScript error (non-fatal): \(errorMessage)") + executionError = errorMessage + } else { + executionError = errorMessage + } + } + } + + // POST-OPEN_APP FOCUS VERIFICATION — in strict mode, use FocusManager to + // confirm the opened app is actually frontmost after openApp execution. + if strictVisualQa && agentAction.type == .openApp && executionError == nil { + let openedAppBundleId = agentAction.appBundleId + let openedAppName = agentAction.appName + if openedAppBundleId != nil || openedAppName != nil { + let openAppFocus = await focusManager.acquireVerifiedFocus( + bundleId: openedAppBundleId, + appName: openedAppName, + maxRetries: 2 + ) + if case .failed(let reason) = openAppFocus { + log.error("[\(action.stepNumber)] Strict QA: open_app focus verification failed: \(reason, privacy: .public)") + isCancelled = true + state = .failed(reason: "FOCUS_ACQUIRE_FAILED: open_app '\(openedAppName ?? openedAppBundleId ?? "unknown")' — \(reason)") + logger.finishSession(result: "failed: open_app focus verification — \(reason)") + if shouldFinalizeRecording { + await finalizeRecording() + } + do { + try daemonClient.send(CuSessionAbortMessage(sessionId: id)) + } catch { + log.error("Failed to send session abort after open_app focus failure: \(error)") + } + return + } + } + } + + // POST-EXECUTION FOCUS VERIFICATION — in strict mode, verify target app + // is still frontmost after the action. In strict mode, drift is terminal. + if strictVisualQa && (targetAppBundleId != nil || targetAppName != nil) { + let postActionFocus = await focusManager.acquireVerifiedFocus( + bundleId: targetAppBundleId, + appName: targetAppName, + maxRetries: 1 + ) + if case .failed(let reason) = postActionFocus { + log.error("[\(action.stepNumber)] Strict QA: post-action focus drift detected: \(reason, privacy: .public)") + isCancelled = true + state = .failed(reason: "FOCUS_ACQUIRE_FAILED: post-action drift — \(reason)") + logger.finishSession(result: "failed: post-action focus drift — \(reason)") + if shouldFinalizeRecording { + await finalizeRecording() + } + do { + try daemonClient.send(CuSessionAbortMessage(sessionId: id)) + } catch { + log.error("Failed to send session abort after post-action focus drift: \(error)") + } + return } } @@ -388,6 +793,34 @@ final class ComputerUseSession: ObservableObject { state = .thinking(step: action.stepNumber + 1, maxSteps: maxSteps) } + // MARK: - Frontmost App Guard + + /// Returns an error message if the frontmost app doesn't match the target and + /// activation retry failed; returns nil if the action should proceed normally. + private func checkFrontmostAppGuard(for action: AgentAction) async -> String? { + let destructiveTypes: Set = [.click, .doubleClick, .rightClick, .type, .key, .scroll, .drag] + guard destructiveTypes.contains(action.type) else { return nil } + + // No target constraint — always pass + guard targetAppBundleId != nil || targetAppName != nil else { return nil } + + let maxRetries = strictVisualQa ? 2 : 1 + let result = await focusManager.acquireVerifiedFocus( + bundleId: targetAppBundleId, + appName: targetAppName, + maxRetries: maxRetries + ) + + switch result { + case .success: + return nil + case .targetNotRunning: + return "FOCUS_ACQUIRE_FAILED: Target app '\(targetAppName ?? targetAppBundleId ?? "unknown")' is not running." + case .failed(let reason): + return "FOCUS_ACQUIRE_FAILED: \(reason)" + } + } + // MARK: - Observation Builder private func buildObservation(executionResult: String?, executionError: String?) async -> CuObservationMessage? { @@ -399,6 +832,7 @@ final class ComputerUseSession: ObservableObject { var axDiffText: String? var secondaryWindowsText: String? var primaryPID: pid_t? + var mergedExecutionError = executionError let stepNumber = currentStepNumber + 1 @@ -412,7 +846,40 @@ final class ComputerUseSession: ObservableObject { if let result = enumerator.enumerateCurrentWindow() { // On first step with Chrome: check if web content is visible. + // Skip this check when targeting an external app — showing the + // NSAlert would activate Vellum and steal focus from the app + // under test. + let isExternalTarget: Bool + if let bundleId = targetAppBundleId, !bundleId.isEmpty { + let selfBundleId = Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant" + isExternalTarget = bundleId != selfBundleId + } else if let name = targetAppName, !name.isEmpty { + let lower = name.lowercased() + isExternalTarget = lower != "vellum" && lower != "vellum assistant" + } else { + isExternalTarget = false + } + + // SELF-TARGET DRIFT DETECTION — if this session explicitly targets Vellum + // but the frontmost app is something else, the AX tree may be from the wrong app. + // Surface a warning in executionError so the agent can react (e.g., refocus Vellum). + let isSelfTargeted = !isExternalTarget && (targetAppBundleId != nil || targetAppName != nil) + if isSelfTargeted { + let selfBundleId = Bundle.main.bundleIdentifier ?? "com.vellum.vellum-assistant" + if let frontApp = NSWorkspace.shared.frontmostApplication, + frontApp.bundleIdentifier != selfBundleId { + let driftMsg = "SELF_TARGET_FOCUS_DRIFT: Session targets Vellum but frontmost app is '\(frontApp.localizedName ?? frontApp.bundleIdentifier ?? "unknown")'. AX tree may be from wrong app." + log.warning("\(driftMsg)") + if let existing = mergedExecutionError { + mergedExecutionError = "\(existing)\n\(driftMsg)" + } else { + mergedExecutionError = driftMsg + } + } + } + if !didChromeAccessibilityCheck, + !isExternalTarget, let frontApp = NSWorkspace.shared.frontmostApplication, ChromeAccessibilityHelper.isChromium(frontApp), !ChromeAccessibilityHelper.hasWebContent(elements: result.elements) { @@ -561,6 +1028,11 @@ final class ComputerUseSession: ObservableObject { } } + // Capture frontmost app info for the daemon's focus evidence tracking + let frontmostApp = NSWorkspace.shared.frontmostApplication + let frontmostAppName = frontmostApp?.localizedName + let frontmostBundleId = frontmostApp?.bundleIdentifier + let observation = CuObservationMessage( sessionId: id, axTree: axTreeInline, @@ -574,9 +1046,11 @@ final class ComputerUseSession: ObservableObject { coordinateOrigin: screenSizePt != nil ? "top_left" : nil, captureDisplayId: screenshotMetadata.map { Double($0.captureDisplayId) }, executionResult: executionResult, - executionError: executionError, + executionError: mergedExecutionError, axTreeBlob: axTreeBlobRef, - screenshotBlob: screenshotBlobRef + screenshotBlob: screenshotBlobRef, + frontmostAppName: frontmostAppName, + frontmostBundleId: frontmostBundleId ) let screenshotRawBytes = screenshotData?.count ?? 0 @@ -635,6 +1109,8 @@ final class ComputerUseSession: ObservableObject { ?? extractInt(from: msg.input, key: "wait_duration") let appName = msg.input["app_name"]?.value as? String ?? msg.input["appName"]?.value as? String + let appBundleId = msg.input["app_bundle_id"]?.value as? String + ?? msg.input["appBundleId"]?.value as? String let script = msg.input["script"]?.value as? String let elementId = extractInt(from: msg.input, key: "element_id") ?? extractInt(from: msg.input, key: "elementId") @@ -657,10 +1133,12 @@ final class ComputerUseSession: ObservableObject { summary: summary, waitDuration: waitDuration, appName: appName, + appBundleId: appBundleId, script: script, resolvedFromElementId: elementId, resolvedToElementId: toElementId, - elementDescription: elementDescription + elementDescription: elementDescription, + requireExactAppMatch: strictVisualQa ) } @@ -910,6 +1388,163 @@ final class ComputerUseSession: ObservableObject { .flatMap { $0.toolCalls } } + // MARK: - Recording Finalization + + /// Validates that a video file is playable by checking for a video track with non-zero dimensions and duration. + private func validateVideoPlayability(at url: URL) async -> Bool { + let asset = AVAsset(url: url) + do { + let tracks = try await asset.load(.tracks) + guard let videoTrack = tracks.first(where: { $0.mediaType == .video }) else { + log.warning("Recording: salvage video has no video track at \(url.lastPathComponent)") + return false + } + let size = try await videoTrack.load(.naturalSize) + guard size.width > 0, size.height > 0 else { + log.warning("Recording: salvage video has zero-size video track at \(url.lastPathComponent)") + return false + } + let duration = try await asset.load(.duration) + guard CMTimeGetSeconds(duration) > 0 else { + log.warning("Recording: salvage video has zero duration at \(url.lastPathComponent)") + return false + } + return true + } catch { + log.warning("Recording: salvage video validation failed at \(url.lastPathComponent): \(error.localizedDescription)") + return false + } + } + + /// Stops the screen recorder (if active) and sends a `cu_session_finalized` message to the daemon. + private func finalizeRecording() async { + defer { didFinalizeRecording = true } + + // Map SessionState to a status string + let status: String + let summary: String + let stepCount: Int + switch state { + case .completed(let s, let steps): + status = "completed" + summary = s + stepCount = steps + case .responded(let answer, let steps): + status = "responded" + summary = answer + stepCount = steps + case .failed(let reason): + status = "failed" + summary = reason + stepCount = currentStepNumber + case .cancelled: + status = "cancelled" + summary = "Session cancelled by user" + stepCount = currentStepNumber + default: + status = "failed" + summary = "Session ended in unexpected state: \(state)" + stepCount = currentStepNumber + } + + // Stop the recorder and gather metadata + var recordingData: IPCCuSessionFinalizedRecording? + if let recorder = screenRecorder, recorder.isRecording { + do { + let result = try await recorder.stopRecording() + let expiresAtEpoch = Int(Date().addingTimeInterval(Double(retentionDays) * 24 * 3600).timeIntervalSince1970 * 1000) + recordingData = IPCCuSessionFinalizedRecording( + localPath: result.fileURL.path, + mimeType: result.mimeType, + sizeBytes: result.sizeBytes, + durationMs: result.durationMs, + width: result.width, + height: result.height, + captureScope: result.captureScope, + includeAudio: result.includeAudio, + targetBundleId: result.targetBundleId, + expiresAt: expiresAtEpoch + ) + isRecordingActive = false + log.info("Recording finalized: \(result.fileURL.lastPathComponent) (\(result.sizeBytes) bytes, \(result.durationMs)ms)") + // Notify daemon that recording stopped successfully + try? daemonClient.send(CuRecordingStatusMessage(sessionId: id, status: "stopped")) + } catch { + isRecordingActive = false + let reason: String + if let recorderError = error as? ScreenRecorderError { + reason = recorderError.errorDescription ?? error.localizedDescription + } else { + reason = error.localizedDescription + } + log.error("Recording: failed to stop screen recording: \(reason)") + try? daemonClient.send(CuRecordingStatusMessage(sessionId: id, status: "failed", reason: reason)) + if qaRecordingWarningMessage == nil { + qaRecordingWarningMessage = "Unable to finalize recording. \(reason)" + } + // Salvage: if the partial file exists on disk AND is playable, track it for cleanup + if let salvageURL = (recorder as? ScreenRecorder)?.lastRecordingFileURL, + FileManager.default.fileExists(atPath: salvageURL.path), + await validateVideoPlayability(at: salvageURL) { + let attrs = try? FileManager.default.attributesOfItem(atPath: salvageURL.path) + let sizeBytes = (attrs?[.size] as? Int) ?? 0 + let expiresAtEpoch = Int(Date().addingTimeInterval(Double(retentionDays) * 24 * 3600).timeIntervalSince1970 * 1000) + log.info("Recording: salvaging partial recording at \(salvageURL.lastPathComponent) (\(sizeBytes) bytes)") + recordingData = IPCCuSessionFinalizedRecording( + localPath: salvageURL.path, + mimeType: "video/mp4", + sizeBytes: sizeBytes, + durationMs: 0, + width: 0, + height: 0, + captureScope: captureScope, + includeAudio: includeAudio, + targetBundleId: nil, + expiresAt: expiresAtEpoch + ) + } else if let salvageURL = (recorder as? ScreenRecorder)?.lastRecordingFileURL, + FileManager.default.fileExists(atPath: salvageURL.path) { + log.warning("Recording: salvage recording at \(salvageURL.lastPathComponent) failed playability check — discarding") + } + } + } + + // If recording was required but no artifact was produced, mark as failure + if requiresRecording && recordingData == nil && status != "failed" { + log.error("Recording: recording was required but no recording artifact exists") + // Override status to failed — the session cannot be considered successful without a recording + let failedStatus = "failed" + let failedSummary = "Recording required but no recording artifact produced" + do { + try daemonClient.send(CuSessionFinalizedMessage( + sessionId: id, + status: failedStatus, + summary: failedSummary, + stepCount: stepCount, + recording: nil + )) + log.info("Recording: sent cu_session_finalized for session \(self.id) (status: \(failedStatus))") + } catch { + log.error("Recording: failed to send cu_session_finalized: \(error.localizedDescription)") + } + return + } + + // Send cu_session_finalized to the daemon + do { + try daemonClient.send(CuSessionFinalizedMessage( + sessionId: id, + status: status, + summary: summary, + stepCount: stepCount, + recording: recordingData + )) + log.info("Recording: sent cu_session_finalized for session \(self.id) (status: \(status))") + } catch { + log.error("Recording: failed to send cu_session_finalized: \(error.localizedDescription)") + } + } + // MARK: - Control func pause() { @@ -927,11 +1562,24 @@ final class ComputerUseSession: ObservableObject { messageLoopTask?.cancel() confirmationContinuation?.resume(returning: false) confirmationContinuation = nil - // Tell the daemon to abort the server-side CU session so it stops burning tokens - do { - try daemonClient.send(CuSessionAbortMessage(sessionId: id)) - } catch { - log.error("Failed to send session abort on cancel: \(error)") + pendingToolPermissionPrompt = nil + + if shouldFinalizeRecording { + // Deferred abort: give run() a chance to send finalization first, + // but guarantee abort eventually fires as a safety net in case + // run() never reaches the post-loop block (e.g., throws or gets stuck). + cancelSafetyNetTask = Task { @MainActor in + try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds + guard !Task.isCancelled else { return } // disarmed by run() post-loop + guard self.isCancelled else { return } // in case state changed + try? self.daemonClient.send(CuSessionAbortMessage(sessionId: self.id)) + } + } else { + do { + try daemonClient.send(CuSessionAbortMessage(sessionId: id)) + } catch { + log.error("Failed to send session abort on cancel: \(error)") + } } } @@ -955,4 +1603,59 @@ final class ComputerUseSession: ObservableObject { } } } + + func presentToolPermissionPrompt(_ message: ConfirmationRequestMessage) { + pendingToolPermissionPrompt = PendingToolPermissionPrompt( + requestId: message.requestId, + toolName: message.toolName, + riskLevel: message.riskLevel, + summary: Self.summarizeToolPermissionInput(message.input) + ) + log.info("CU prompt surfaced in overlay: requestId=\(message.requestId, privacy: .public), tool=\(message.toolName, privacy: .public)") + } + + func approveToolPermissionPrompt() { + respondToToolPermissionPrompt(decision: "allow") + } + + func denyToolPermissionPrompt() { + respondToToolPermissionPrompt(decision: "deny") + } + + private func respondToToolPermissionPrompt(decision: String) { + guard let pending = pendingToolPermissionPrompt else { return } + do { + try daemonClient.send(ConfirmationResponseMessage( + requestId: pending.requestId, + decision: decision + )) + pendingToolPermissionPrompt = nil + log.info("CU prompt resolved in overlay: requestId=\(pending.requestId, privacy: .public), decision=\(decision, privacy: .public)") + } catch { + log.error("Failed to send CU tool confirmation response: \(error.localizedDescription)") + } + } + + private static func summarizeToolPermissionInput(_ input: [String: AnyCodable]) -> String { + if let appName = input["appName"]?.value as? String, !appName.isEmpty { + return "Target app: \(appName)" + } + if let reasoning = input["reasoning"]?.value as? String, !reasoning.isEmpty { + return reasoning + } + if let command = input["command"]?.value as? String, !command.isEmpty { + return command + } + if input.isEmpty { + return "No additional details provided." + } + let text = input + .sorted { $0.key < $1.key } + .map { key, value in + let rendered = value.value.map { String(describing: $0) } ?? "null" + return "\(key)=\(rendered)" + } + .joined(separator: ", ") + return text.count > 220 ? String(text.prefix(220)) + "..." : text + } } diff --git a/clients/macos/vellum-assistant/Features/Chat/MediaEmbeds/InlineVideoAttachmentView.swift b/clients/macos/vellum-assistant/Features/Chat/MediaEmbeds/InlineVideoAttachmentView.swift index e4fd0f03f4c..696924344bd 100644 --- a/clients/macos/vellum-assistant/Features/Chat/MediaEmbeds/InlineVideoAttachmentView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/MediaEmbeds/InlineVideoAttachmentView.swift @@ -87,6 +87,9 @@ struct InlineVideoAttachmentView: View { .frame(maxWidth: 360) .aspectRatio(videoAspectRatio, contentMode: .fit) .onHover { isHovering = $0 } + .onDrag { + dragItemProvider() + } .onDisappear { player?.pause() player = nil @@ -170,8 +173,15 @@ struct InlineVideoAttachmentView: View { // Server thumbnail and aspect ratio are set eagerly in init. if thumbnailImage != nil { return } - // Fallback: extract thumbnail from inline video data. - guard !attachment.data.isEmpty, let data = Data(base64Encoded: attachment.data) else { return } + // Try inline base64 first, then fall back to /content for lazy-loaded attachments. + var videoData: Data? + if !attachment.data.isEmpty { + videoData = Data(base64Encoded: attachment.data) + } else if attachment.isLazyLoad, let port = daemonHttpPort, !attachment.id.isEmpty { + videoData = try? await fetchAttachmentContent(port: port, attachmentId: attachment.id) + } + + guard let data = videoData else { return } let fileURL = safeTempURL() do { @@ -269,9 +279,11 @@ struct InlineVideoAttachmentView: View { isLoading = true Task { do { - let base64 = try await fetchAttachmentData(port: port, attachmentId: attachmentId) + let data = try await fetchAttachmentContent(port: port, attachmentId: attachmentId) + let fileURL = safeTempURL() + try data.write(to: fileURL) await MainActor.run { isLoading = false } - await playFromBase64(base64) + await playFromFile(fileURL) } catch { log.error("Failed to fetch attachment \(attachmentId): \(error.localizedDescription)") await MainActor.run { @@ -315,11 +327,7 @@ struct InlineVideoAttachmentView: View { } Task { do { - let base64 = try await fetchAttachmentData(port: port, attachmentId: attachmentId) - guard let data = Data(base64Encoded: base64) else { - await MainActor.run { isSaving = false } - return - } + let data = try await fetchAttachmentContent(port: port, attachmentId: attachmentId) try data.write(to: destURL) await MainActor.run { isSaving = false } } catch { @@ -339,6 +347,52 @@ struct InlineVideoAttachmentView: View { } } + /// Creates an NSItemProvider for drag-and-drop to Finder or other apps. + /// Uses the cached temp file if available, otherwise writes inline data to disk first. + private func dragItemProvider() -> NSItemProvider { + if let fileURL = cachedFileURL { + return NSItemProvider(contentsOf: fileURL) ?? NSItemProvider() + } + + // Write inline base64 data to a temp file for dragging + if !attachment.data.isEmpty, let data = Data(base64Encoded: attachment.data) { + let fileURL = safeTempURL() + do { + try data.write(to: fileURL) + return NSItemProvider(contentsOf: fileURL) ?? NSItemProvider() + } catch { + log.warning("Failed to write video for drag: \(error.localizedDescription)") + } + } + + // Fallback: provide the filename as a promise (lazy-loaded attachments) + if attachment.isLazyLoad, let port = daemonHttpPort, !attachment.id.isEmpty { + let provider = NSItemProvider() + let fileURL = safeTempURL() + let attachmentId = attachment.id + provider.suggestedName = (attachment.filename as NSString).lastPathComponent + provider.registerFileRepresentation( + forTypeIdentifier: "public.mpeg-4", + fileOptions: [], + visibility: .all + ) { completion in + Task { + do { + let data = try await fetchAttachmentContent(port: port, attachmentId: attachmentId) + try data.write(to: fileURL) + completion(fileURL, true, nil) + } catch { + completion(nil, false, error) + } + } + return nil + } + return provider + } + + return NSItemProvider() + } + private func openInExternalPlayer() { if let fileURL = cachedFileURL { NSWorkspace.shared.open(fileURL) @@ -347,14 +401,7 @@ struct InlineVideoAttachmentView: View { isLoading = true Task { do { - let base64 = try await fetchAttachmentData(port: port, attachmentId: attachmentId) - guard let data = Data(base64Encoded: base64) else { - await MainActor.run { - isLoading = false - failed = true - } - return - } + let data = try await fetchAttachmentContent(port: port, attachmentId: attachmentId) let fileURL = safeTempURL() try data.write(to: fileURL) await MainActor.run { @@ -362,7 +409,10 @@ struct InlineVideoAttachmentView: View { NSWorkspace.shared.open(fileURL) } } catch { - await MainActor.run { isLoading = false } + await MainActor.run { + isLoading = false + failed = true + } } } } else { @@ -374,8 +424,8 @@ struct InlineVideoAttachmentView: View { } } -/// Fetch attachment base64 data from the daemon HTTP endpoint. -private func fetchAttachmentData(port: Int, attachmentId: String) async throws -> String { +/// Read the daemon HTTP auth token from disk. +private func readDaemonToken() throws -> String { let tokenBase: String if let baseDir = ProcessInfo.processInfo.environment["BASE_DATA_DIR"]?.trimmingCharacters(in: .whitespacesAndNewlines), !baseDir.isEmpty { @@ -389,6 +439,12 @@ private func fetchAttachmentData(port: Int, attachmentId: String) async throws - !token.isEmpty else { throw URLError(.userAuthenticationRequired) } + return token +} + +/// Fetch attachment base64 data from the daemon HTTP endpoint. +private func fetchAttachmentData(port: Int, attachmentId: String) async throws -> String { + let token = try readDaemonToken() let url = URL(string: "http://localhost:\(port)/v1/attachments/\(attachmentId)")! var request = URLRequest(url: url) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") @@ -405,6 +461,20 @@ private func fetchAttachmentData(port: Int, attachmentId: String) async throws - return decoded.data } +/// Fetch attachment binary content from the daemon /content endpoint. +/// Returns raw bytes suitable for writing to disk or AVPlayer consumption. +private func fetchAttachmentContent(port: Int, attachmentId: String) async throws -> Data { + let token = try readDaemonToken() + var request = URLRequest(url: URL(string: "http://127.0.0.1:\(port)/v1/attachments/\(attachmentId)/content")!) + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { + throw URLError(.badServerResponse) + } + return data +} + /// NSViewRepresentable wrapper for AVPlayerView. private struct VideoPlayerView: NSViewRepresentable { let player: AVPlayer diff --git a/clients/macos/vellum-assistant/Features/MainWindow/MainWindow.swift b/clients/macos/vellum-assistant/Features/MainWindow/MainWindow.swift index 8278022c4f6..d0530f9f1d6 100644 --- a/clients/macos/vellum-assistant/Features/MainWindow/MainWindow.swift +++ b/clients/macos/vellum-assistant/Features/MainWindow/MainWindow.swift @@ -15,6 +15,10 @@ import SwiftUI class TitleBarZoomableWindow: NSWindow { private var preZoomFrame: NSRect? + /// When true, title-bar double-click minimize is suppressed to prevent + /// focus loss during focus-lock QA sessions. + var suppressMinimize = false + /// Weak reference to the composer text view so we can redirect typing to it. weak var composerTextView: NSTextView? @@ -69,6 +73,7 @@ class TitleBarZoomableWindow: NSWindow { let action = UserDefaults.standard.string(forKey: "AppleActionOnDoubleClick") ?? "Maximize" switch action { case "Minimize": + if suppressMinimize { return } miniaturize(nil) case "None": break @@ -347,6 +352,11 @@ final class MainWindow { )) } + /// Suppress or restore title-bar minimize for focus-lock sessions. + func setSuppressMinimize(_ suppress: Bool) { + (window as? TitleBarZoomableWindow)?.suppressMinimize = suppress + } + /// Hide the window without destroying it (can be restored with `show()`). func hide() { window?.orderOut(nil) diff --git a/clients/macos/vellum-assistant/Features/Session/SessionOverlayView.swift b/clients/macos/vellum-assistant/Features/Session/SessionOverlayView.swift index 45baaf3de8c..8c8e173aa08 100644 --- a/clients/macos/vellum-assistant/Features/Session/SessionOverlayView.swift +++ b/clients/macos/vellum-assistant/Features/Session/SessionOverlayView.swift @@ -4,6 +4,44 @@ import SwiftUI struct SessionOverlayView: View { @ObservedObject var session: ComputerUseSession + private let minOverlayWidth: CGFloat = 340 + private let maxOverlayWidth: CGFloat = 560 + + private var overlayWidth: CGFloat { + let length = longestVisibleTextLength + if length > 220 { return maxOverlayWidth } + if length > 140 { return 500 } + if length > 90 { return 420 } + return minOverlayWidth + } + + private var longestVisibleTextLength: Int { + var candidates: [String] = [session.task] + if let prompt = session.pendingToolPermissionPrompt { + candidates.append(prompt.toolName) + candidates.append(prompt.summary) + } + if let warning = session.qaRecordingWarningMessage { + candidates.append(warning) + } + switch session.state { + case .running(_, _, let lastAction, let reasoning): + candidates.append(lastAction) + candidates.append(reasoning) + case .awaitingConfirmation(let reason): + candidates.append(reason) + case .completed(let summary, _): + candidates.append(summary) + case .responded(let answer, _): + candidates.append(answer) + case .failed(let reason): + candidates.append(reason) + case .idle, .thinking, .paused, .cancelled: + break + } + return candidates.map(\.count).max() ?? 0 + } + var body: some View { VStack(alignment: .leading, spacing: VSpacing.md + VSpacing.xxs) { // Header @@ -19,10 +57,18 @@ struct SessionOverlayView: View { Text(session.task) .font(VFont.caption) .foregroundStyle(.secondary) - .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) Divider() + // Recording status indicator (QA mode only) + recordingStatusView + + if let prompt = session.pendingToolPermissionPrompt { + toolPermissionPromptView(prompt) + Divider() + } + // State-dependent content stateContent @@ -30,7 +76,41 @@ struct SessionOverlayView: View { controlButtons } .padding(14) - .frame(width: 340, alignment: .leading) + .frame(width: overlayWidth, alignment: .leading) + } + + private func toolPermissionPromptView(_ prompt: PendingToolPermissionPrompt) -> some View { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 4) { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.yellow) + Text("Permission needed") + .font(.caption.bold()) + } + + Text("Tool: \(prompt.toolName) (\(prompt.riskLevel))") + .font(.caption) + .foregroundStyle(.primary) + + Text(prompt.summary) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + + HStack(spacing: 8) { + Button("Allow") { + session.approveToolPermissionPrompt() + } + .buttonStyle(.borderedProminent) + .tint(.blue) + .controlSize(.small) + + Button("Deny") { + session.denyToolPermissionPrompt() + } + .controlSize(.small) + } + } } @ViewBuilder @@ -67,14 +147,14 @@ struct SessionOverlayView: View { Text(reasoning) .font(.caption) .foregroundStyle(.primary) - .lineLimit(3) + .fixedSize(horizontal: false, vertical: true) .padding(.leading, 6) } } Text(lastAction) .font(.caption) .foregroundStyle(.secondary) - .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) } case .paused(let step, let maxSteps): @@ -126,7 +206,7 @@ struct SessionOverlayView: View { Text(summary) .font(.caption) .foregroundStyle(.secondary) - .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) } } @@ -146,16 +226,21 @@ struct SessionOverlayView: View { } .frame(maxHeight: 200) } - .frame(width: 380) case .failed(let reason): - HStack(spacing: 6) { + HStack(alignment: .top, spacing: 6) { Image(systemName: "xmark.circle.fill") .foregroundStyle(.red) - Text(reason) - .font(.caption) - .foregroundStyle(.secondary) - .lineLimit(3) + .padding(.top, 1) + ScrollView { + Text(reason) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + .textSelection(.enabled) + } + .frame(maxHeight: 120) } case .cancelled: @@ -165,6 +250,64 @@ struct SessionOverlayView: View { } } + @ViewBuilder + private var recordingStatusView: some View { + if session.qaMode { + if let warning = session.qaRecordingWarningMessage { + HStack(alignment: .top, spacing: VSpacing.sm) { + Circle() + .fill(VColor.error) + .frame(width: 8, height: 8) + .padding(.top, 3) + ScrollView { + Text(warning) + .font(VFont.caption) + .foregroundColor(VColor.error) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + .textSelection(.enabled) + } + .frame(maxHeight: 80) + } + .padding(.horizontal, VSpacing.xs) + } else if session.isRecordingActive { + HStack(spacing: VSpacing.sm) { + Circle() + .fill(VColor.success) + .frame(width: 8, height: 8) + Text("Recording") + .font(VFont.caption) + .foregroundColor(VColor.success) + } + .padding(.horizontal, VSpacing.xs) + } else if session.requiresRecording { + HStack(spacing: VSpacing.sm) { + Circle() + .fill(VColor.warning) + .frame(width: 8, height: 8) + Text("Recording required \u{2014} waiting...") + .font(VFont.caption) + .foregroundColor(VColor.warning) + } + .padding(.horizontal, VSpacing.xs) + } else { + switch session.state { + case .idle, .running(step: 0, _, _, _): + HStack(spacing: VSpacing.sm) { + ProgressView() + .controlSize(.mini) + Text("Recording starting...") + .font(VFont.caption) + .foregroundStyle(.secondary) + } + .padding(.horizontal, VSpacing.xs) + default: + EmptyView() + } + } + } + } + @ViewBuilder private var controlButtons: some View { switch session.state { diff --git a/clients/macos/vellum-assistant/Features/Session/SessionOverlayWindow.swift b/clients/macos/vellum-assistant/Features/Session/SessionOverlayWindow.swift index c60136f02ec..90b6af1bbf6 100644 --- a/clients/macos/vellum-assistant/Features/Session/SessionOverlayWindow.swift +++ b/clients/macos/vellum-assistant/Features/Session/SessionOverlayWindow.swift @@ -6,7 +6,7 @@ import SwiftUI final class SessionOverlayWindow { private var panel: NSPanel? private let session: ComputerUseSession - private var stateCancellable: AnyCancellable? + private var layoutCancellable: AnyCancellable? init(session: ComputerUseSession) { self.session = session @@ -34,19 +34,25 @@ final class SessionOverlayWindow { // Size window to fit SwiftUI content and resize on every state change sizeAndPosition(panel) - stateCancellable = session.$state + layoutCancellable = session.objectWillChange .sink { [weak self, weak panel] _ in guard let self, let panel else { return } - self.sizeAndPosition(panel) + DispatchQueue.main.async { + self.sizeAndPosition(panel) + } } panel.orderFront(nil) self.panel = panel } + func bringToFront() { + panel?.orderFrontRegardless() + } + func close() { - stateCancellable?.cancel() - stateCancellable = nil + layoutCancellable?.cancel() + layoutCancellable = nil panel?.close() panel = nil } diff --git a/clients/macos/vellum-assistantTests/FocusReliabilityTests.swift b/clients/macos/vellum-assistantTests/FocusReliabilityTests.swift new file mode 100644 index 00000000000..23f3ed68ad2 --- /dev/null +++ b/clients/macos/vellum-assistantTests/FocusReliabilityTests.swift @@ -0,0 +1,254 @@ +import XCTest +@testable import VellumAssistantLib + +// MARK: - AXElementRegistry Tests + +final class AXElementRegistryTests: XCTestCase { + + func testRegisterAndResolve() { + let registry = AXElementRegistry() + let pid = ProcessInfo.processInfo.processIdentifier + let element = AXUIElementCreateApplication(pid) + + registry.register(elementId: 42, element: element) + + XCTAssertNotNil(registry.resolve(elementId: 42)) + XCTAssertNil(registry.resolve(elementId: 99), "Unregistered ID should return nil") + XCTAssertEqual(registry.count, 1) + } + + func testClearRemovesAllEntries() { + let registry = AXElementRegistry() + let pid = ProcessInfo.processInfo.processIdentifier + let element = AXUIElementCreateApplication(pid) + + registry.register(elementId: 1, element: element) + registry.register(elementId: 2, element: element) + XCTAssertEqual(registry.count, 2) + + registry.clear() + XCTAssertEqual(registry.count, 0) + XCTAssertNil(registry.resolve(elementId: 1)) + XCTAssertNil(registry.resolve(elementId: 2)) + } + + func testResolveStaleIdReturnsNil() { + let registry = AXElementRegistry() + let pid = ProcessInfo.processInfo.processIdentifier + let element = AXUIElementCreateApplication(pid) + + registry.register(elementId: 5, element: element) + registry.clear() + registry.register(elementId: 10, element: element) + + // Old ID should be gone + XCTAssertNil(registry.resolve(elementId: 5)) + // New ID should work + XCTAssertNotNil(registry.resolve(elementId: 10)) + } +} + +// MARK: - FocusManager Tests + +final class FocusManagerTests: XCTestCase { + + @MainActor + func testNoTargetConstraint_alwaysSucceeds() async { + let manager = FocusManager() + let result = await manager.acquireVerifiedFocus(bundleId: nil, appName: nil) + // No target = always matches whatever is frontmost + if case .success = result { + // expected + } else { + XCTFail("Expected success when no target is specified, got \(result)") + } + } + + @MainActor + func testNonexistentApp_returnsNotRunning() async { + let manager = FocusManager() + let result = await manager.acquireVerifiedFocus( + bundleId: "com.nonexistent.bogusapp.doesnotexist", + appName: "BogusAppThatDoesNotExist" + ) + if case .targetNotRunning = result { + // expected + } else { + XCTFail("Expected targetNotRunning for nonexistent app, got \(result)") + } + } + + @MainActor + func testEmptyBundleId_fallsToName() async { + let manager = FocusManager() + // Empty bundle ID should be treated as "no bundle ID" + let result = await manager.acquireVerifiedFocus( + bundleId: "", + appName: "NonexistentApp999" + ) + if case .targetNotRunning = result { + // expected — app name doesn't match any running app + } else { + XCTFail("Expected targetNotRunning for empty bundleId + unknown name, got \(result)") + } + } +} + +// MARK: - AXActionExecutor Tests + +final class AXActionExecutorTests: XCTestCase { + + @MainActor + func testClickUnregisteredElement_returnsFallback() { + let registry = AXElementRegistry() + let executor = AXActionExecutor(elementRegistry: registry) + + let result = executor.click(elementId: 999) + if case .fallback(let reason) = result { + XCTAssertTrue(reason.contains("not in registry")) + } else { + XCTFail("Expected fallback for unregistered element") + } + } + + @MainActor + func testTypeUnregisteredElement_returnsFallback() { + let registry = AXElementRegistry() + let executor = AXActionExecutor(elementRegistry: registry) + + let result = executor.type(elementId: 999, text: "hello") + if case .fallback(let reason) = result { + XCTAssertTrue(reason.contains("not in registry")) + } else { + XCTFail("Expected fallback for unregistered element") + } + } + + @MainActor + func testFocusUnregisteredElement_returnsFallback() { + let registry = AXElementRegistry() + let executor = AXActionExecutor(elementRegistry: registry) + + let result = executor.focus(elementId: 999) + if case .fallback(let reason) = result { + XCTAssertTrue(reason.contains("not in registry")) + } else { + XCTFail("Expected fallback for unregistered element") + } + } +} + +// MARK: - ActionTargetMode Tests + +final class ActionTargetModeTests: XCTestCase { + + func testTargetModeAX_whenOnlyElementId() { + let action = AgentAction( + type: .click, + reasoning: "click button", + resolvedFromElementId: 5 + ) + XCTAssertEqual(action.targetMode, .ax) + } + + func testTargetModeVision_whenOnlyCoordinates() { + let action = AgentAction( + type: .click, + reasoning: "click button", + x: 100, + y: 200 + ) + XCTAssertEqual(action.targetMode, .vision) + } + + func testTargetModeMixed_whenBothElementIdAndCoordinates() { + let action = AgentAction( + type: .click, + reasoning: "click button", + x: 100, + y: 200, + resolvedFromElementId: 5 + ) + XCTAssertEqual(action.targetMode, .mixed) + } + + func testTargetModeUnknown_whenNeitherElementIdNorCoordinates() { + let action = AgentAction( + type: .type, + reasoning: "type text", + text: "hello" + ) + XCTAssertEqual(action.targetMode, .unknown) + } + + func testDragTargetModeMixed_whenBothSourceAndDestinationElementIds() { + let action = AgentAction( + type: .drag, + reasoning: "drag", + x: 10, y: 20, + toX: 30, toY: 40, + resolvedFromElementId: 1, + resolvedToElementId: 2 + ) + XCTAssertEqual(action.targetMode, .mixed) + } +} + +// MARK: - ExecutorError.focusAcquireFailed Tests + +final class ExecutorFocusAcquireFailedTests: XCTestCase { + + func testFocusAcquireFailed_errorDescription() { + let error = ExecutorError.focusAcquireFailed("Expected 'Safari' but frontmost is 'Finder'") + XCTAssertEqual(error.errorDescription, "FOCUS_ACQUIRE_FAILED: Expected 'Safari' but frontmost is 'Finder'") + } + + func testFocusAcquireFailed_isLocalizedError() { + let error: Error = ExecutorError.focusAcquireFailed("test reason") + XCTAssertTrue(error.localizedDescription.contains("FOCUS_ACQUIRE_FAILED")) + } +} + +// MARK: - Strict QA Session Focus Tests + +final class StrictQAFocusTests: XCTestCase { + + /// In strict QA, post-action focus drift should fail the session (not just warn). + @MainActor + func testStrictQA_postActionFocusDrift_failsRun() async { + // This test verifies the contract: when strictVisualQa is true and + // the target app is not frontmost after an action, the session transitions + // to .failed state rather than appending an error and continuing. + // + // We can't easily mock FocusManager (it's internal to Session), but we + // verify the error message format matches the strict failure path. + let error = ExecutorError.focusAcquireFailed("post-action drift — Could not activate 'TestApp' after 1 attempts.") + XCTAssertTrue(error.localizedDescription.contains("FOCUS_ACQUIRE_FAILED")) + XCTAssertTrue(error.localizedDescription.contains("post-action drift")) + } + + /// Non-strict sessions should NOT hard-fail on the same conditions. + func testNonStrict_focusDrift_noHardFail() { + // In non-strict mode, focus drift appends to executionError but does not + // terminate the session. This is a design invariant test. + let action = AgentAction( + type: .click, + reasoning: "click button", + x: 100, + y: 200 + ) + // Non-strict actions should not have requireExactAppMatch set + XCTAssertFalse(action.requireExactAppMatch) + } + + /// Strict QA actions should set requireExactAppMatch. + func testStrictQA_setsRequireExactAppMatch() { + let action = AgentAction( + type: .openApp, + reasoning: "open app", + appName: "Safari", + requireExactAppMatch: true + ) + XCTAssertTrue(action.requireExactAppMatch) + } +} diff --git a/clients/macos/vellum-assistantTests/SessionTests.swift b/clients/macos/vellum-assistantTests/SessionTests.swift index 350c49e7a3d..660ea26e412 100644 --- a/clients/macos/vellum-assistantTests/SessionTests.swift +++ b/clients/macos/vellum-assistantTests/SessionTests.swift @@ -55,6 +55,8 @@ final class MockDaemonClient: DaemonClientProtocol { final class MockAccessibilityTreeEnumerator: AccessibilityTreeProviding { var result: (elements: [AXElement], windowTitle: String, appName: String, pid: pid_t)? var secondaryWindowCallCount = 0 + let elementRegistry: AXElementRegistry? = nil + var allowSelfEnumeration: Bool = false init(result: (elements: [AXElement], windowTitle: String, appName: String)? = nil) { if let r = result { @@ -1672,6 +1674,68 @@ final class SessionTests: XCTestCase { XCTAssertGreaterThan(inlineLargeJSON.count, 400_000) XCTAssertLessThan(blobLargeJSON.count, 1_000) } + + // MARK: - Self-Enumeration + + @MainActor + func testAllowSelfEnumeration_defaultsFalse() async { + let enumerator = AccessibilityTreeEnumerator() + XCTAssertFalse(enumerator.allowSelfEnumeration, "allowSelfEnumeration should default to false") + } + + @MainActor + func testMockAllowSelfEnumeration_defaultsFalse() async { + let mock = MockAccessibilityTreeEnumerator() + XCTAssertFalse(mock.allowSelfEnumeration, "Mock allowSelfEnumeration should default to false") + } + + @MainActor + func testSelfTargetedSession_includesVellumAXTree() async { + let daemonClient = MockDaemonClient() + let continuation = daemonClient.setupTestStream() + let enumerator = MockAccessibilityTreeEnumerator( + result: (elements: makeTestElements(), windowTitle: "Vellum Window", appName: "Vellum") + ) + // Simulate a self-targeted session — the enumerator should be used as-is + let session = ComputerUseSession( + task: "test self-targeted", + daemonClient: daemonClient, + enumerator: enumerator, + screenCapture: MockScreenCapture(), + executor: MockActionExecutor(), + maxSteps: 50, + initialDelayMs: 0, + adaptiveDelay: false, + targetAppName: "Vellum" + ) + + let runTask = Task { @MainActor in + await session.run() + } + + try? await Task.sleep(nanoseconds: 50_000_000) + + // The observation should contain the AX tree from the enumerator (Vellum's own window) + let obsMessages = daemonClient.sentMessages.compactMap { $0 as? CuObservationMessage } + XCTAssertGreaterThanOrEqual(obsMessages.count, 1, "Should have sent at least one observation") + let firstObs = obsMessages[0] + XCTAssertNotNil(firstObs.axTree, "Self-targeted session should include AX tree") + XCTAssertTrue(firstObs.axTree?.contains("Vellum") == true, "AX tree should reference Vellum app") + + // Clean up + continuation.yield(.cuComplete(makeCompleteMessage( + sessionId: session.id, + summary: "Self-target done", + stepCount: 1 + ))) + await runTask.value + + if case .completed(let summary, _) = session.state { + XCTAssertEqual(summary, "Self-target done") + } else { + XCTFail("Expected completed state, got \(session.state)") + } + } } /// Test elements with 3+ interactive elements (enough for screenshot skip) diff --git a/clients/shared/DesignSystem/Core/Feedback/VToast.swift b/clients/shared/DesignSystem/Core/Feedback/VToast.swift index 30eed2af1c0..f5940852643 100644 --- a/clients/shared/DesignSystem/Core/Feedback/VToast.swift +++ b/clients/shared/DesignSystem/Core/Feedback/VToast.swift @@ -39,13 +39,16 @@ public struct VToast: View { } public var body: some View { - HStack(spacing: VSpacing.md) { + HStack(alignment: .top, spacing: VSpacing.md) { Image(systemName: iconName) .foregroundColor(iconColor) Text(message) .font(VFont.body) .foregroundColor(VColor.textPrimary) - .lineLimit(3) + .multilineTextAlignment(.leading) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .layoutPriority(1) Spacer(minLength: 0) diff --git a/clients/shared/IPC/Generated/IPCContractGenerated.swift b/clients/shared/IPC/Generated/IPCContractGenerated.swift index 1fead40c44c..846d7af0fd4 100644 --- a/clients/shared/IPC/Generated/IPCContractGenerated.swift +++ b/clients/shared/IPC/Generated/IPCContractGenerated.swift @@ -973,6 +973,18 @@ public struct IPCCuAction: Codable, Sendable { } } +public struct IPCCuAutoApproveUpdate: Codable, Sendable { + public let type: String + public let sessionId: String + public let enabled: Bool + + public init(type: String, sessionId: String, enabled: Bool) { + self.type = type + self.sessionId = sessionId + self.enabled = enabled + } +} + public struct IPCCuComplete: Codable, Sendable { public let type: String public let sessionId: String @@ -1024,8 +1036,12 @@ public struct IPCCuObservation: Codable, Sendable { public let executionError: String? public let axTreeBlob: IPCIpcBlobRef? public let screenshotBlob: IPCIpcBlobRef? + /// Name of the frontmost application at observation time. + public let frontmostAppName: String? + /// Bundle ID of the frontmost application at observation time. + public let frontmostBundleId: String? - public init(type: String, sessionId: String, axTree: String? = nil, axDiff: String? = nil, secondaryWindows: String? = nil, screenshot: String? = nil, screenshotWidthPx: Double? = nil, screenshotHeightPx: Double? = nil, screenWidthPt: Double? = nil, screenHeightPt: Double? = nil, coordinateOrigin: String? = nil, captureDisplayId: Double? = nil, executionResult: String? = nil, executionError: String? = nil, axTreeBlob: IPCIpcBlobRef? = nil, screenshotBlob: IPCIpcBlobRef? = nil) { + public init(type: String, sessionId: String, axTree: String? = nil, axDiff: String? = nil, secondaryWindows: String? = nil, screenshot: String? = nil, screenshotWidthPx: Double? = nil, screenshotHeightPx: Double? = nil, screenWidthPt: Double? = nil, screenHeightPt: Double? = nil, coordinateOrigin: String? = nil, captureDisplayId: Double? = nil, executionResult: String? = nil, executionError: String? = nil, axTreeBlob: IPCIpcBlobRef? = nil, screenshotBlob: IPCIpcBlobRef? = nil, frontmostAppName: String? = nil, frontmostBundleId: String? = nil) { self.type = type self.sessionId = sessionId self.axTree = axTree @@ -1042,6 +1058,22 @@ public struct IPCCuObservation: Codable, Sendable { self.executionError = executionError self.axTreeBlob = axTreeBlob self.screenshotBlob = screenshotBlob + self.frontmostAppName = frontmostAppName + self.frontmostBundleId = frontmostBundleId + } +} + +public struct IPCCuRecordingStatus: Codable, Sendable { + public let type: String + public let sessionId: String + public let status: String + public let reason: String? + + public init(type: String, sessionId: String, status: String, reason: String? = nil) { + self.type = type + self.sessionId = sessionId + self.status = status + self.reason = reason } } @@ -1063,8 +1095,20 @@ public struct IPCCuSessionCreate: Codable, Sendable { public let screenHeight: Int public let attachments: [IPCUserMessageAttachment]? public let interactionType: String? - - public init(type: String, sessionId: String, task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCUserMessageAttachment]? = nil, interactionType: String? = nil) { + /// Origin chat session for result injection (QA workflow). + public let reportToSessionId: String? + /// Marks this CU run as a QA/test workflow. + public let qaMode: Bool? + /// Optional target app name constraint for disambiguation. + public let targetAppName: String? + /// Optional target app bundle identifier for disambiguation. + public let targetAppBundleId: String? + /// When true, recording MUST start before any destructive action. + public let requiresRecording: Bool? + /// When true, target app must be visually frontmost during interaction and recording must be valid. + public let strictVisualQa: Bool? + + public init(type: String, sessionId: String, task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCUserMessageAttachment]? = nil, interactionType: String? = nil, reportToSessionId: String? = nil, qaMode: Bool? = nil, targetAppName: String? = nil, targetAppBundleId: String? = nil, requiresRecording: Bool? = nil, strictVisualQa: Bool? = nil) { self.type = type self.sessionId = sessionId self.task = task @@ -1072,6 +1116,56 @@ public struct IPCCuSessionCreate: Codable, Sendable { self.screenHeight = screenHeight self.attachments = attachments self.interactionType = interactionType + self.reportToSessionId = reportToSessionId + self.qaMode = qaMode + self.targetAppName = targetAppName + self.targetAppBundleId = targetAppBundleId + self.requiresRecording = requiresRecording + self.strictVisualQa = strictVisualQa + } +} + +public struct IPCCuSessionFinalized: Codable, Sendable { + public let type: String + public let sessionId: String + public let status: String + public let summary: String + public let stepCount: Int + public let recording: IPCCuSessionFinalizedRecording? + + public init(type: String, sessionId: String, status: String, summary: String, stepCount: Int, recording: IPCCuSessionFinalizedRecording? = nil) { + self.type = type + self.sessionId = sessionId + self.status = status + self.summary = summary + self.stepCount = stepCount + self.recording = recording + } +} + +public struct IPCCuSessionFinalizedRecording: Codable, Sendable { + public let localPath: String + public let mimeType: String + public let sizeBytes: Int + public let durationMs: Int + public let width: Int + public let height: Int + public let captureScope: String + public let includeAudio: Bool + public let targetBundleId: String? + public let expiresAt: Int? + + public init(localPath: String, mimeType: String, sizeBytes: Int, durationMs: Int, width: Int, height: Int, captureScope: String, includeAudio: Bool, targetBundleId: String? = nil, expiresAt: Int? = nil) { + self.localPath = localPath + self.mimeType = mimeType + self.sizeBytes = sizeBytes + self.durationMs = durationMs + self.width = width + self.height = height + self.captureScope = captureScope + self.includeAudio = includeAudio + self.targetBundleId = targetBundleId + self.expiresAt = expiresAt } } @@ -2161,10 +2255,10 @@ public struct IPCMemoryRecalled: Codable, Sendable { public let selectedCount: Int public let rerankApplied: Bool public let injectedTokens: Int - public let latencyMs: Double + public let latencyMs: Int public let topCandidates: [IPCMemoryRecalledCandidateDebug] - public init(type: String, provider: String, model: String, lexicalHits: Double, semanticHits: Double, recencyHits: Double, entityHits: Double, relationSeedEntityCount: Int? = nil, relationTraversedEdgeCount: Int? = nil, relationNeighborEntityCount: Int? = nil, relationExpandedItemCount: Int? = nil, earlyTerminated: Bool? = nil, mergedCount: Int, selectedCount: Int, rerankApplied: Bool, injectedTokens: Int, latencyMs: Double, topCandidates: [IPCMemoryRecalledCandidateDebug]) { + public init(type: String, provider: String, model: String, lexicalHits: Double, semanticHits: Double, recencyHits: Double, entityHits: Double, relationSeedEntityCount: Int? = nil, relationTraversedEdgeCount: Int? = nil, relationNeighborEntityCount: Int? = nil, relationExpandedItemCount: Int? = nil, earlyTerminated: Bool? = nil, mergedCount: Int, selectedCount: Int, rerankApplied: Bool, injectedTokens: Int, latencyMs: Int, topCandidates: [IPCMemoryRecalledCandidateDebug]) { self.type = type self.provider = provider self.model = model @@ -2215,13 +2309,13 @@ public struct IPCMemoryStatus: Codable, Sendable { public let model: String? public let conflictsPending: Double public let conflictsResolved: Double - public let oldestPendingConflictAgeMs: Double? + public let oldestPendingConflictAgeMs: Int? public let cleanupResolvedJobsPending: Double public let cleanupSupersededJobsPending: Double public let cleanupResolvedJobsCompleted24h: Double public let cleanupSupersededJobsCompleted24h: Double - public init(type: String, enabled: Bool, degraded: Bool, reason: String? = nil, provider: String? = nil, model: String? = nil, conflictsPending: Double, conflictsResolved: Double, oldestPendingConflictAgeMs: Double?, cleanupResolvedJobsPending: Double, cleanupSupersededJobsPending: Double, cleanupResolvedJobsCompleted24h: Double, cleanupSupersededJobsCompleted24h: Double) { + public init(type: String, enabled: Bool, degraded: Bool, reason: String? = nil, provider: String? = nil, model: String? = nil, conflictsPending: Double, conflictsResolved: Double, oldestPendingConflictAgeMs: Int?, cleanupResolvedJobsPending: Double, cleanupSupersededJobsPending: Double, cleanupResolvedJobsCompleted24h: Double, cleanupSupersededJobsCompleted24h: Double) { self.type = type self.enabled = enabled self.degraded = degraded @@ -2981,14 +3075,16 @@ public struct IPCSessionListResponseSession: Codable, Sendable { public let source: String? /// Channel binding metadata exposed in session/conversation list APIs. public let channelBinding: IPCChannelBinding? + public let conversationOriginChannel: String? - public init(id: String, title: String, updatedAt: Int, threadType: String? = nil, source: String? = nil, channelBinding: IPCChannelBinding? = nil) { + public init(id: String, title: String, updatedAt: Int, threadType: String? = nil, source: String? = nil, channelBinding: IPCChannelBinding? = nil, conversationOriginChannel: String? = nil) { self.id = id self.title = title self.updatedAt = updatedAt self.threadType = threadType self.source = source self.channelBinding = channelBinding + self.conversationOriginChannel = conversationOriginChannel } } @@ -3739,13 +3835,40 @@ public struct IPCTaskRouted: Codable, Sendable { public let task: String? /// Set when a text_qa session escalates to computer_use via computer_use_request_control. public let escalatedFrom: String? - - public init(type: String, sessionId: String, interactionType: String, task: String? = nil, escalatedFrom: String? = nil) { + /// Whether this is a QA/test workflow session. + public let qaMode: Bool? + /// The originating chat session ID for result injection. + public let reportToSessionId: String? + /// Recording retention in days (from daemon config). + public let retentionDays: Double? + /// Capture scope for QA recording (from daemon config). + public let captureScope: String? + /// Whether to include audio in QA recording (from daemon config). + public let includeAudio: Bool? + /// Target app name for frontmost-app guard (from target-app-hints). + public let targetAppName: String? + /// Target app bundle ID for frontmost-app guard (from target-app-hints). + public let targetAppBundleId: String? + /// When true, recording MUST start before any destructive action. + public let requiresRecording: Bool? + /// When true, target app must be visually frontmost during interaction and recording must be valid. + public let strictVisualQa: Bool? + + public init(type: String, sessionId: String, interactionType: String, task: String? = nil, escalatedFrom: String? = nil, qaMode: Bool? = nil, reportToSessionId: String? = nil, retentionDays: Double? = nil, captureScope: String? = nil, includeAudio: Bool? = nil, targetAppName: String? = nil, targetAppBundleId: String? = nil, requiresRecording: Bool? = nil, strictVisualQa: Bool? = nil) { self.type = type self.sessionId = sessionId self.interactionType = interactionType self.task = task self.escalatedFrom = escalatedFrom + self.qaMode = qaMode + self.reportToSessionId = reportToSessionId + self.retentionDays = retentionDays + self.captureScope = captureScope + self.includeAudio = includeAudio + self.targetAppName = targetAppName + self.targetAppBundleId = targetAppBundleId + self.requiresRecording = requiresRecording + self.strictVisualQa = strictVisualQa } } @@ -3780,14 +3903,20 @@ public struct IPCTaskSubmit: Codable, Sendable { public let screenHeight: Int public let attachments: [IPCUserMessageAttachment]? public let source: String? + /// When set, overrides the QA-based requiresRecording computation. + public let requiresRecording: Bool? + /// Active conversation/thread ID — used for QA latch tracking and reportToSessionId. + public let conversationId: String? - public init(type: String, task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCUserMessageAttachment]? = nil, source: String? = nil) { + public init(type: String, task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCUserMessageAttachment]? = nil, source: String? = nil, requiresRecording: Bool? = nil, conversationId: String? = nil) { self.type = type self.task = task self.screenWidth = screenWidth self.screenHeight = screenHeight self.attachments = attachments self.source = source + self.requiresRecording = requiresRecording + self.conversationId = conversationId } } diff --git a/clients/shared/IPC/IPCMessages.swift b/clients/shared/IPC/IPCMessages.swift index 1376e97bd6b..4cce5b6d96f 100644 --- a/clients/shared/IPC/IPCMessages.swift +++ b/clients/shared/IPC/IPCMessages.swift @@ -148,11 +148,33 @@ extension IPCUserMessageAttachment { public typealias CuSessionCreateMessage = IPCCuSessionCreate extension IPCCuSessionCreate { - public init(sessionId: String, task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCAttachment]?, interactionType: String?) { - self.init(type: "cu_session_create", sessionId: sessionId, task: task, screenWidth: screenWidth, screenHeight: screenHeight, attachments: attachments, interactionType: interactionType) + public init(sessionId: String, task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCAttachment]?, interactionType: String?, reportToSessionId: String? = nil, qaMode: Bool? = nil, targetAppName: String? = nil, targetAppBundleId: String? = nil, requiresRecording: Bool? = nil) { + self.init(type: "cu_session_create", sessionId: sessionId, task: task, screenWidth: screenWidth, screenHeight: screenHeight, attachments: attachments, interactionType: interactionType, reportToSessionId: reportToSessionId, qaMode: qaMode, targetAppName: targetAppName, targetAppBundleId: targetAppBundleId, requiresRecording: requiresRecording) } } +/// Sent by the client to report recording lifecycle events. +/// Backed by generated `IPCCuRecordingStatus`. +public typealias CuRecordingStatusMessage = IPCCuRecordingStatus + +extension IPCCuRecordingStatus { + public init(sessionId: String, status: String, reason: String? = nil) { + self.init(type: "cu_recording_status", sessionId: sessionId, status: status, reason: reason) + } +} + +/// Sent when a CU session reaches a terminal state (QA mode). +/// Backed by generated `IPCCuSessionFinalized`. +public typealias CuSessionFinalizedMessage = IPCCuSessionFinalized + +extension IPCCuSessionFinalized { + public init(sessionId: String, status: String, summary: String, stepCount: Int, recording: IPCCuSessionFinalizedRecording?) { + self.init(type: "cu_session_finalized", sessionId: sessionId, status: status, summary: summary, stepCount: stepCount, recording: recording) + } +} + +// IPCCuSessionFinalizedRecording uses the generated memberwise init directly. + /// Sent after each perceive step with AX tree, screenshot, and execution results. /// Backed by generated `IPCCuObservation`. public typealias CuObservationMessage = IPCCuObservation @@ -173,7 +195,9 @@ extension IPCCuObservation { executionResult: String?, executionError: String?, axTreeBlob: IPCIpcBlobRef? = nil, - screenshotBlob: IPCIpcBlobRef? = nil + screenshotBlob: IPCIpcBlobRef? = nil, + frontmostAppName: String? = nil, + frontmostBundleId: String? = nil ) { self.init( type: "cu_observation", @@ -191,7 +215,9 @@ extension IPCCuObservation { executionResult: executionResult, executionError: executionError, axTreeBlob: axTreeBlob, - screenshotBlob: screenshotBlob + screenshotBlob: screenshotBlob, + frontmostAppName: frontmostAppName, + frontmostBundleId: frontmostBundleId ) } } @@ -301,8 +327,8 @@ extension IPCUserMessage { public typealias TaskSubmitMessage = IPCTaskSubmit extension IPCTaskSubmit { - public init(task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCAttachment]?, source: String?) { - self.init(type: "task_submit", task: task, screenWidth: screenWidth, screenHeight: screenHeight, attachments: attachments, source: source) + public init(task: String, screenWidth: Int, screenHeight: Int, attachments: [IPCAttachment]?, source: String?, conversationId: String? = nil) { + self.init(type: "task_submit", task: task, screenWidth: screenWidth, screenHeight: screenHeight, attachments: attachments, source: source, conversationId: conversationId) } } @@ -326,6 +352,16 @@ extension IPCCuSessionAbort { } } +/// Sent to notify the daemon of auto-approve toggle changes for a CU session. +/// Backed by generated `IPCCuAutoApproveUpdate`. +public typealias CuAutoApproveUpdateMessage = IPCCuAutoApproveUpdate + +extension IPCCuAutoApproveUpdate { + public init(sessionId: String, enabled: Bool) { + self.init(type: "cu_auto_approve_update", sessionId: sessionId, enabled: enabled) + } +} + /// Authenticate to the daemon on initial socket connect. /// Backed by generated `IPCAuthMessage`. public typealias AuthMessage = IPCAuthMessage @@ -772,7 +808,7 @@ extension IPCMemoryRecalled { selectedCount: Int, rerankApplied: Bool, injectedTokens: Int, - latencyMs: Double, + latencyMs: Int, topCandidates: [IPCMemoryRecalledCandidateDebug] ) { self.init(