diff --git a/agent/lib/worker-cancellation-delivery.ts b/agent/lib/worker-cancellation-delivery.ts index 7a08d8b4..990df51e 100644 --- a/agent/lib/worker-cancellation-delivery.ts +++ b/agent/lib/worker-cancellation-delivery.ts @@ -1,3 +1,5 @@ +import { parseWorkerTaskNotification } from "@/lib/eve-task-notifications"; + const runtime = globalThis as typeof globalThis & { openInstinctWorkerCancellationTurns?: Map; }; @@ -9,10 +11,10 @@ export function recordWorkerCancellationTurn( turnId: string, message: string ) { - const taskId = /^Background task (\S+) \(worker\) is cancelled\.$/u.exec( - message - )?.[1]; - if (taskId) cancellationTurns.set(turnKey(sessionId, turnId), taskId); + const notification = parseWorkerTaskNotification(message); + if (notification?.kind === "cancelled") { + cancellationTurns.set(turnKey(sessionId, turnId), notification.taskId); + } } export function consumeWorkerCancellationTurn( diff --git a/app/(authenticated)/(manager)/chat/_components/agent-chat.tsx b/app/(authenticated)/(manager)/chat/_components/agent-chat.tsx index e9413ff4..0dfabb28 100644 --- a/app/(authenticated)/(manager)/chat/_components/agent-chat.tsx +++ b/app/(authenticated)/(manager)/chat/_components/agent-chat.tsx @@ -26,16 +26,13 @@ import { Shimmer } from "@/components/ai-elements/shimmer"; import { summarizeChatUsage } from "@/app/(authenticated)/(manager)/_lib/chat-usage"; import { getLatestTurnFailure } from "@/app/(authenticated)/(manager)/chat/_lib/turn-failure"; import type { ChatUsage } from "@/lib/chat"; +import { parseWorkerTaskNotification } from "@/lib/eve-task-notifications"; import { cn } from "@/lib/utils"; import { AgentMessage } from "./agent-message"; import { collectSubagentSessions } from "@/app/_lib/subagent-sessions"; import { SubagentPanel } from "./subagent-panel"; const AGENT_NAME = "Local Vault Assistant"; -const backgroundWorkerDelivery = - /^Background task (\S+) \(worker\) (?:update: |needs input\.$|is cancelled\.$|is completed\.\n\nResult:\n|failed\.\n\nError:\n)/u; -const backgroundWorkerAuthorization = - /^Background task (\S+) needs authorization\.$/u; const taskCancelResultSchema = z.object({ kind: z.literal("tool-result"), output: z.object({ tasks: z.array(z.unknown()) }), @@ -401,13 +398,10 @@ export function backgroundWorkerDeliveryMessageIds( } if (event.type !== "message.received") continue; - const taskId = - backgroundWorkerDelivery.exec(event.data.message)?.[1] ?? - backgroundWorkerAuthorization.exec(event.data.message)?.[1]; + const notification = parseWorkerTaskNotification(event.data.message); + const taskId = notification?.taskId; if (taskId && taskIds.has(taskId)) { - const isCancellation = event.data.message.endsWith( - "(worker) is cancelled." - ); + const isCancellation = notification.kind === "cancelled"; if (!isCancellation) messageIds.add(`${event.data.turnId}:user`); if (isCancellation && cancelledTaskIds.delete(taskId)) { messageIds.add(`${event.data.turnId}:user`); @@ -454,16 +448,9 @@ function hasPendingBackgroundWorker(events: readonly MessageStreamEvent[]) { } if (event.type !== "message.received") continue; - const deliveredTaskId = - backgroundWorkerDelivery.exec(event.data.message)?.[1] ?? - backgroundWorkerAuthorization.exec(event.data.message)?.[1]; - if ( - deliveredTaskId && - !event.data.message.startsWith( - `Background task ${deliveredTaskId} (worker) update: ` - ) - ) { - taskIds.delete(deliveredTaskId); + const notification = parseWorkerTaskNotification(event.data.message); + if (notification && notification.kind !== "update") { + taskIds.delete(notification.taskId); } } diff --git a/lib/browser/benchmark.ts b/lib/browser/benchmark.ts index 8a0e7cd8..ceefc32f 100644 --- a/lib/browser/benchmark.ts +++ b/lib/browser/benchmark.ts @@ -1,8 +1,8 @@ import { z } from "zod"; import type { MessageStreamEvent } from "eve/client"; +import { parseWorkerTaskNotification } from "../eve-task-notifications"; import { parseTaskCompletionOutput } from "../task-completion"; -const workerTaskNotificationPrefix = /^Background task (\S+) \(worker\) /u; const terminalTaskControlSchema = z.object({ tasks: z.array( z.object({ @@ -144,34 +144,19 @@ export function readTaskCompletion(events: readonly MessageStreamEvent[]) { function readWorkerTaskNotification(event: MessageStreamEvent) { if (event.type !== "message.received") return undefined; - const match = workerTaskNotificationPrefix.exec(event.data.message); - if (!match) return undefined; - const [, taskId] = match; - if (!taskId) return undefined; - const message = event.data.message.slice(match[0].length); - - if (message === "is cancelled.") - return { status: "cancelled" as const, taskId }; - - const completedPrefix = "is completed.\n\nResult:\n"; - if (message.startsWith(completedPrefix)) { - return { - output: message.slice(completedPrefix.length), - status: "completed" as const, - taskId, - }; - } - - const failedPrefix = "failed.\n\nError:\n"; - if (message.startsWith(failedPrefix)) { - return { - output: message.slice(failedPrefix.length), - status: "failed" as const, - taskId, - }; + const notification = parseWorkerTaskNotification(event.data.message); + if ( + notification?.kind !== "cancelled" && + notification?.kind !== "completed" && + notification?.kind !== "failed" + ) { + return undefined; } - - return undefined; + return { + output: notification.output, + status: notification.kind, + taskId: notification.taskId, + }; } export function readBackgroundWorkerTasks( diff --git a/lib/eve-task-notifications.ts b/lib/eve-task-notifications.ts new file mode 100644 index 00000000..1f5bdad3 --- /dev/null +++ b/lib/eve-task-notifications.ts @@ -0,0 +1,67 @@ +// Eve limitation: background-task delivery has no typed event, only these prose messages, forcing this custom parser. +type TaskNotification = + | { readonly kind: "authorization"; readonly taskId: string } + | TaskAgentNotification; + +interface TaskAgentNotification { + readonly agentName: string; + readonly kind: + | "cancelled" + | "completed" + | "failed" + | "needs-input" + | "update"; + readonly output?: string; + readonly taskId: string; +} + +const notificationPrefix = /^Background task (\S+) \(([\w-]+)\) /u; +const authorizationNotification = + /^Background task (\S+) needs authorization\.$/u; +const completedPrefix = "is completed.\n\nResult:\n"; +const failedPrefix = "failed.\n\nError:\n"; + +function parseTaskNotification(message: string): TaskNotification | undefined { + const authorized = authorizationNotification.exec(message)?.[1]; + if (authorized) return { kind: "authorization", taskId: authorized }; + + const match = notificationPrefix.exec(message); + const taskId = match?.[1]; + const agentName = match?.[2]; + if (!match || !taskId || !agentName) return undefined; + const rest = message.slice(match[0].length); + + if (rest === "is cancelled.") return { agentName, kind: "cancelled", taskId }; + if (rest === "needs input.") + return { agentName, kind: "needs-input", taskId }; + if (rest.startsWith("update: ")) return { agentName, kind: "update", taskId }; + if (rest.startsWith(completedPrefix)) { + return { + agentName, + kind: "completed", + output: rest.slice(completedPrefix.length), + taskId, + }; + } + if (rest.startsWith(failedPrefix)) { + return { + agentName, + kind: "failed", + output: rest.slice(failedPrefix.length), + taskId, + }; + } + return undefined; +} + +export function parseWorkerTaskNotification(message: string) { + const notification = parseTaskNotification(message); + if (!notification) return undefined; + if ( + notification.kind !== "authorization" && + notification.agentName !== "worker" + ) { + return undefined; + } + return notification; +} diff --git a/lib/manager/chrome-passwords.ts b/lib/manager/chrome-passwords.ts index 387cf06c..2e815f17 100644 --- a/lib/manager/chrome-passwords.ts +++ b/lib/manager/chrome-passwords.ts @@ -1,3 +1,4 @@ +import Papa from "papaparse"; import type { ManagerMutation } from "."; import { loginIdentifierSchema, @@ -15,13 +16,8 @@ type VaultImportItem = Extract< >["items"][number]; export function parseChromePasswordsCsv(csv: string) { - const rows = parseCsv(csv); - const headers = rows.shift()?.map((header) => - header - .replace(/^\uFEFF/, "") - .trim() - .toLowerCase() - ); + const rows = Papa.parse(csv.replace(/^\uFEFF/u, "")).data; + const headers = rows.shift()?.map((header) => header.trim().toLowerCase()); if (!headers) throw new Error("Choose a Chrome passwords CSV file."); const indexes = { @@ -116,49 +112,3 @@ function originFromUrl(value: string) { return undefined; } } - -function parseCsv(csv: string) { - const rows: string[][] = []; - let field = ""; - let quoted = false; - let row: string[] = []; - - for (let index = 0; index < csv.length; index += 1) { - const character = csv.charAt(index); - if (quoted) { - if (character === '"') { - if (csv[index + 1] === '"') { - field += '"'; - index += 1; - } else { - quoted = false; - } - } else { - field += character; - } - continue; - } - - if (character === '"' && field.length === 0) { - quoted = true; - } else if (character === ",") { - row.push(field); - field = ""; - } else if (character === "\n" || character === "\r") { - if (character === "\r" && csv[index + 1] === "\n") index += 1; - row.push(field); - rows.push(row); - field = ""; - row = []; - } else { - field += character; - } - } - - if (quoted) throw new Error("This CSV has an unfinished quoted value."); - if (field.length > 0 || row.length > 0) { - row.push(field); - rows.push(row); - } - return rows; -} diff --git a/lib/manager/server/kernel-native-autofill.ts b/lib/manager/server/kernel-native-autofill.ts index d1e69388..aadd72af 100644 --- a/lib/manager/server/kernel-native-autofill.ts +++ b/lib/manager/server/kernel-native-autofill.ts @@ -1,4 +1,5 @@ import Kernel from "@onkernel/sdk"; +import CDP from "chrome-remote-interface"; import { z } from "zod"; import { env } from "../../env"; import type { AutofillClaim } from "../vault-autofill-protocol"; @@ -573,128 +574,59 @@ async function withKernelPage( } } +type RawCdpSend = ( + method: string, + params?: object, + sessionId?: string +) => Promise; + class CdpConnection { - readonly #pending = new Map< - number, - { - readonly reject: (reason?: unknown) => void; - readonly resolve: (value: unknown) => void; - } - >(); - #nextId = 1; - - private constructor( - private readonly socket: WebSocket, - signal: AbortSignal | undefined - ) { - socket.addEventListener("message", (event) => { - this.#onMessage(event); - }); - socket.addEventListener("close", () => { - this.#rejectPending(new Error("The Kernel CDP connection closed.")); - }); + private readonly rawSend: RawCdpSend; + + private constructor(private readonly client: CDP.Client) { + this.rawSend = client.send.bind(client); + } + + static async connect(url: string, signal?: AbortSignal) { + signal?.throwIfAborted(); + const client = await CDP({ local: true, target: url }).catch( + (cause: unknown) => { + throw new Error("Could not connect to the Kernel browser over CDP.", { + cause, + }); + } + ); signal?.addEventListener( "abort", () => { - this.close(); + void client.close().catch(() => undefined); }, { once: true } ); + return new CdpConnection(client); } - static async connect(url: string, signal?: AbortSignal) { - const socket = new WebSocket(url); - await new Promise((resolve, reject) => { - const cleanup = () => { - socket.removeEventListener("open", onOpen); - socket.removeEventListener("error", onError); - signal?.removeEventListener("abort", onAbort); - }; - const onOpen = () => { - cleanup(); - resolve(); - }; - const onError = () => { - cleanup(); - reject(new Error("Could not connect to the Kernel browser over CDP.")); - }; - const onAbort = () => { - cleanup(); - socket.close(); - reject( - signal?.reason instanceof Error - ? signal.reason - : new Error("The CDP connection was aborted.") - ); - }; - socket.addEventListener("open", onOpen, { once: true }); - socket.addEventListener("error", onError, { once: true }); - signal?.addEventListener("abort", onAbort, { once: true }); - }); - return new CdpConnection(socket, signal); - } - - send(method: string, params?: object, sessionId?: string) { - const id = this.#nextId++; - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - this.#pending.delete(id); - reject(new Error(`Chromium did not respond to ${method}.`)); - }, 15_000); - this.#pending.set(id, { - reject(reason) { - clearTimeout(timeout); - reject( - reason instanceof Error - ? reason - : new Error("The Chromium command failed.") - ); - }, - resolve(value) { - clearTimeout(timeout); - resolve(value); - }, - }); - this.socket.send(JSON.stringify({ id, method, params, sessionId })); - }); - } - - close() { - this.socket.close(); - } - - #onMessage(event: MessageEvent) { - if (typeof event.data !== "string") return; - let rawMessage: unknown; + async send(method: string, params?: object, sessionId?: string) { + let deadline: ReturnType | undefined; try { - rawMessage = JSON.parse(event.data); - } catch { - return; - } - const message = cdpResponseSchema.safeParse(rawMessage); - if (!message.success || message.data.id === undefined) return; - const pending = this.#pending.get(message.data.id); - if (!pending) return; - this.#pending.delete(message.data.id); - if (message.data.error) { - pending.reject(new Error(message.data.error.message)); - } else { - pending.resolve(message.data.result); + return await Promise.race([ + this.rawSend(method, params, sessionId), + new Promise((_, reject) => { + deadline = setTimeout(() => { + reject(new Error(`Chromium did not respond to ${method}.`)); + }, 15_000); + }), + ]); + } finally { + clearTimeout(deadline); } } - #rejectPending(error: Error) { - for (const { reject } of this.#pending.values()) reject(error); - this.#pending.clear(); + close() { + void this.client.close().catch(() => undefined); } } -const cdpResponseSchema = z.object({ - error: z.object({ message: z.string() }).optional(), - id: z.number().int().optional(), - result: z.unknown().optional(), -}); - function flattenFrames( node: z.infer ): { readonly id: string; readonly url: string }[] { diff --git a/package.json b/package.json index fb0e5256..d0e20d4c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@workflow/world-vercel": "5.0.0-beta.40", "ai": "^7.0.79", "better-auth": "1.7.2", + "chrome-remote-interface": "^0.34.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", @@ -33,6 +34,7 @@ "motion": "13.1.1", "nanoid": "6.0.1", "next": "16.3.3", + "papaparse": "^5.7.0", "pg": "^8.23.0", "react": "19.2.8", "react-dom": "19.2.8", @@ -47,7 +49,9 @@ "@electric-sql/pglite": "^0.5.8", "@next/env": "16.3.3", "@oxlint/plugins": "1.80.0", + "@types/chrome-remote-interface": "^0.34.0", "@types/node": "24.13.3", + "@types/papaparse": "^5.5.2", "@types/pg": "^8.23.1", "@types/react": "19.2.18", "@types/react-dom": "19.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 453e5d8d..5fe65efb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: better-auth: specifier: 1.7.2 version: 1.7.2(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@electric-sql/pglite@0.5.8)(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/pg@8.23.1)(kysely@0.29.5)(pg@8.23.0))(next@16.3.3(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.11(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.27.0)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0))) + chrome-remote-interface: + specifier: ^0.34.0 + version: 0.34.0 class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -101,6 +104,9 @@ importers: next: specifier: 16.3.3 version: 16.3.3(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + papaparse: + specifier: ^5.7.0 + version: 5.7.0 pg: specifier: ^8.23.0 version: 8.23.0 @@ -138,9 +144,15 @@ importers: '@oxlint/plugins': specifier: 1.80.0 version: 1.80.0 + '@types/chrome-remote-interface': + specifier: ^0.34.0 + version: 0.34.0 '@types/node': specifier: 24.13.3 version: 24.13.3 + '@types/papaparse': + specifier: ^5.5.2 + version: 5.5.2 '@types/pg': specifier: ^8.23.1 version: 8.23.1 @@ -2880,6 +2892,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/chrome-remote-interface@0.34.0': + resolution: {integrity: sha512-4q8pMUvasrAkB7bejGWw5igboAvBz7vJRZq+295yFONGoeN7X/hfq8Mq4vTxUQKnENbR9tqksP8RZsLxlRtsWQ==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -3012,6 +3027,9 @@ packages: '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/papaparse@5.5.2': + resolution: {integrity: sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==} + '@types/pg@8.23.1': resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} @@ -3646,6 +3664,10 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + chrome-remote-interface@0.34.0: + resolution: {integrity: sha512-rTTcTZ3zemx8I+nvBii7d8BAF0Ms8LLEroypfvwwZOwSpyNGLE28nStXyCA6VwGp2YSQfmCrQH21F/E+oBFvMw==} + hasBin: true + cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} @@ -3697,6 +3719,9 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + commander@2.11.0: + resolution: {integrity: sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -4062,6 +4087,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + devtools-protocol@0.0.1173815: + resolution: {integrity: sha512-CmsjkudmgCMeae8FSJB4GV8COZwOk6dJKyE9HS2F/ih/sA8uTdbPKHg5F7DsrOYLEET/QKK00LJCU4YoyG+uHg==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -5883,6 +5911,9 @@ packages: package-manager-detector@1.8.0: resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + papaparse@5.7.0: + resolution: {integrity: sha512-qBGxg/7Q3Kl9Wfhrz2Z74UnvnHTXLNG6jmKJFeBvP2+y4lV7So+7SR62+Zd47JvdrCkX+nDcnr0ObPzek/+6RA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -7063,6 +7094,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.21.3: resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} @@ -9039,6 +9082,10 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/chrome-remote-interface@0.34.0': + dependencies: + devtools-protocol: 0.0.1173815 + '@types/d3-array@3.2.2': {} '@types/d3-axis@3.0.6': @@ -9194,6 +9241,10 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/papaparse@5.5.2': + dependencies: + '@types/node': 24.13.3 + '@types/pg@8.23.1': dependencies: '@types/node': 24.13.3 @@ -9980,6 +10031,14 @@ snapshots: chownr@3.0.0: {} + chrome-remote-interface@0.34.0: + dependencies: + commander: 2.11.0 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + cjs-module-lexer@1.2.3: {} class-variance-authority@0.7.1: @@ -10024,6 +10083,8 @@ snapshots: commander@14.0.3: {} + commander@2.11.0: {} + commander@7.2.0: {} commander@8.3.0: {} @@ -10347,6 +10408,8 @@ snapshots: dependencies: dequal: 2.0.3 + devtools-protocol@0.0.1173815: {} + diff@8.0.4: {} dompurify@3.4.14: @@ -12564,6 +12627,8 @@ snapshots: package-manager-detector@1.8.0: {} + papaparse@5.7.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -13813,6 +13878,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.13: {} + ws@8.21.3: {} wsl-utils@1.0.0: