diff --git a/extensions/chrome-browser/background.mjs b/extensions/chrome-browser/background.mjs index 39803f41..fcd0a9b6 100644 --- a/extensions/chrome-browser/background.mjs +++ b/extensions/chrome-browser/background.mjs @@ -1,16 +1,31 @@ -import { createBrowserProtocol } from './protocol.mjs'; +import { + EXTENSION_PROTOCOL_VERSION, + MESSAGE_TYPES, + createBrowserProtocol, +} from './protocol.mjs'; import { DEFAULT_SERVER_URL } from './config.mjs'; +import { fetchJsonWithTimeout } from './http.mjs'; const STORAGE_KEYS = ['serverUrl', 'configuredServerUrl', 'token', 'pairingId', 'pairingSecret', 'approvalUrl', 'status', 'extensionName']; -const protocol = createBrowserProtocol(chrome); let socket = null; let reconnectTimer = null; -let suppressSocketClose = false; -const DEFAULT_FETCH_TIMEOUT_MS = 10000; +let connectPromise = null; +let connectionEpoch = 0; +let reconnectAttempt = 0; +let statusUpdateQueue = Promise.resolve(); +let commandQueue = Promise.resolve(); +const activeCommandControllers = new Map(); +const pendingUrlValidations = new Map(); const DEFAULT_WS_CONNECT_TIMEOUT_MS = 10000; +const DEFAULT_URL_VALIDATION_TIMEOUT_MS = 6500; +const MAX_PENDING_URL_VALIDATIONS = 256; +const RECONNECT_BASE_DELAY_MS = 1000; +const RECONNECT_MAX_DELAY_MS = 60 * 1000; const KEEPALIVE_ALARM_NAME = 'neoagent-extension-keepalive'; const KEEPALIVE_ALARM_MINUTES = 1; -const EXTENSION_PROTOCOL_VERSION = 1; +const protocol = createBrowserProtocol(chrome, { + validateUrl: requestUrlValidation, +}); function getStorage(keys = STORAGE_KEYS) { return chrome.storage.local.get(keys); @@ -25,7 +40,24 @@ function removeStorage(keys) { } function normalizeServerUrl(value) { - return String(value || '').trim().replace(/\/+$/, ''); + const raw = String(value || '').trim(); + if (!raw) return ''; + let parsed; + try { + parsed = new URL(raw); + } catch { + throw new Error('NeoAgent server URL is invalid.'); + } + if (!['http:', 'https:'].includes(parsed.protocol)) { + throw new Error('NeoAgent server URL must use http or https.'); + } + if (parsed.username || parsed.password) { + throw new Error('NeoAgent server URL must not contain credentials.'); + } + parsed.search = ''; + parsed.hash = ''; + parsed.pathname = parsed.pathname.replace(/\/+$/, ''); + return parsed.toString().replace(/\/+$/, ''); } function configuredServerUrl() { @@ -40,7 +72,7 @@ async function resolveServerUrl(preferred) { } function websocketUrl(serverUrl, token) { - const url = new URL('/api/browser-extension/ws', serverUrl); + const url = new URL('api/browser-extension/ws', `${serverUrl}/`); url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; // Token in the URL is required for the HTTP upgrade handshake; the browser // WebSocket API does not support custom headers. Ensure the server's access @@ -49,19 +81,6 @@ function websocketUrl(serverUrl, token) { return url.toString(); } -async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - return await fetch(url, { - ...options, - signal: controller.signal, - }); - } finally { - clearTimeout(timer); - } -} - function compareVersions(a, b) { const left = String(a || '0').split('.').map((part) => Number(part) || 0); const right = String(b || '0').split('.').map((part) => Number(part) || 0); @@ -73,9 +92,14 @@ function compareVersions(a, b) { return 0; } -async function updateStatus(status) { - await setStorage({ status }); - chrome.runtime.sendMessage({ type: 'status', status }).catch(() => {}); +async function updateStatus(status, expectedEpoch = null) { + const task = statusUpdateQueue.catch(() => {}).then(async () => { + if (expectedEpoch != null && expectedEpoch !== connectionEpoch) return; + await setStorage({ status }); + await Promise.resolve(chrome.runtime.sendMessage({ type: 'status', status })).catch(() => {}); + }); + statusUpdateQueue = task.catch(() => {}); + return task; } function clearReconnectTimer() { @@ -83,113 +107,298 @@ function clearReconnectTimer() { reconnectTimer = null; } +function scheduleReconnect() { + clearReconnectTimer(); + const exponentialDelay = Math.min( + RECONNECT_MAX_DELAY_MS, + RECONNECT_BASE_DELAY_MS * (2 ** Math.min(reconnectAttempt, 6)), + ); + reconnectAttempt += 1; + const jitterMs = Math.floor(exponentialDelay * Math.random() * 0.25); + reconnectTimer = setTimeout(() => connect().catch(() => {}), exponentialDelay + jitterMs); +} + function ensureKeepaliveAlarm() { chrome.alarms?.create?.(KEEPALIVE_ALARM_NAME, { periodInMinutes: KEEPALIVE_ALARM_MINUTES, }); } -async function handleSocketDisconnected(ws) { - if (socket !== ws) { - return; +function rejectPendingUrlValidations(error, expectedSocket = null) { + for (const entry of Array.from(pendingUrlValidations.values())) { + if (!expectedSocket || entry.socket === expectedSocket) entry.reject(error); + } +} + +function requestUrlValidation(url, options = {}) { + const ws = socket; + if (!ws || ws.readyState !== WebSocket.OPEN) { + return Promise.reject(new Error('NeoAgent browser security validation is unavailable.')); + } + if (pendingUrlValidations.size >= MAX_PENDING_URL_VALIDATIONS) { + return Promise.reject(new Error('Too many browser URL validations are pending.')); } + + const id = crypto.randomUUID(); + const signal = options.signal || null; + return new Promise((resolve, reject) => { + let settled = false; + let timer = null; + const cleanup = () => { + clearTimeout(timer); + signal?.removeEventListener('abort', onAbort); + pendingUrlValidations.delete(id); + }; + const finish = (callback, value) => { + if (settled) return; + settled = true; + cleanup(); + callback(value); + }; + const onAbort = () => finish( + reject, + signal.reason instanceof Error + ? signal.reason + : new Error('Browser URL validation was aborted.'), + ); + const entry = { + socket: ws, + resolve: (allowed) => finish(resolve, allowed === true), + reject: (error) => finish(reject, error), + }; + timer = setTimeout(() => { + entry.reject(new Error('Browser URL validation timed out.')); + }, DEFAULT_URL_VALIDATION_TIMEOUT_MS); + if (signal?.aborted) { + onAbort(); + return; + } + signal?.addEventListener('abort', onAbort, { once: true }); + pendingUrlValidations.set(id, entry); + if (!sendSocketMessage(ws, { + type: MESSAGE_TYPES.URL_VALIDATION_REQUEST, + version: EXTENSION_PROTOCOL_VERSION, + id, + url: String(url || ''), + })) { + entry.reject(new Error('Browser URL validation could not be sent.')); + } + }); +} + +async function handleSocketDisconnected(ws, socketEpoch) { + if (socket !== ws || socketEpoch !== connectionEpoch) return; socket = null; - if (suppressSocketClose) { - suppressSocketClose = false; - return; + const disconnectedEpoch = ++connectionEpoch; + try { + if (ws.readyState !== WebSocket.CLOSED) ws.close(); + } catch {} + for (const controller of activeCommandControllers.values()) { + controller.abort(new Error('NeoAgent browser connection closed.')); } + activeCommandControllers.clear(); + rejectPendingUrlValidations(new Error('NeoAgent browser connection closed.'), ws); const { token } = await getStorage(['token']); + if (connectionEpoch !== disconnectedEpoch || socket) return; if (!token) { - await updateStatus('not_paired'); + await updateStatus('not_paired', disconnectedEpoch); return; } - await updateStatus('disconnected'); - clearReconnectTimer(); - reconnectTimer = setTimeout(() => connect().catch(() => {}), 5000); + await updateStatus('disconnected', disconnectedEpoch); + if (connectionEpoch === disconnectedEpoch && !socket) scheduleReconnect(); } -async function connect() { - const { token } = await getStorage(['token']); - const serverUrl = await resolveServerUrl(); +async function connectOnce() { + const startingEpoch = connectionEpoch; + const { token, serverUrl: storedServerUrl } = await getStorage(['token', 'serverUrl']); + if (startingEpoch !== connectionEpoch) return { connected: false, cancelled: true }; + const serverUrl = normalizeServerUrl(storedServerUrl) || configuredServerUrl(); if (!serverUrl || !token) { - await updateStatus('not_paired'); + await updateStatus('not_paired', startingEpoch); return { connected: false }; } if (socket && socket.readyState === WebSocket.OPEN) { return { connected: true }; } - if (socket) { - try { socket.close(); } catch {} + if (socket && socket.readyState === WebSocket.CONNECTING) { + return { connected: false, connecting: true }; + } + const staleSocket = socket; + socket = null; + if (staleSocket) { + rejectPendingUrlValidations(new Error('NeoAgent browser connection was replaced.'), staleSocket); + try { staleSocket.close(); } catch {} } clearReconnectTimer(); - suppressSocketClose = false; - const ws = new WebSocket(websocketUrl(serverUrl, token)); + const socketEpoch = ++connectionEpoch; + let ws; + try { + ws = new WebSocket(websocketUrl(serverUrl, token)); + } catch (error) { + await updateStatus('disconnected', socketEpoch); + scheduleReconnect(); + throw error; + } socket = ws; - await updateStatus('connecting'); - const connectTimeout = setTimeout(() => { - if (socket === ws && ws.readyState !== WebSocket.OPEN) { - try { ws.close(); } catch {} - } - }, DEFAULT_WS_CONNECT_TIMEOUT_MS); + const ready = new Promise((resolve, reject) => { + let settled = false; + const finish = (callback, value) => { + if (settled) return; + settled = true; + clearTimeout(connectTimeout); + callback(value); + }; + const disconnect = (error, logLabel) => { + finish(reject, error); + handleSocketDisconnected(ws, socketEpoch).catch((disconnectError) => { + console.error(logLabel, disconnectError); + }); + }; + const connectTimeout = setTimeout(() => { + disconnect( + new Error(`NeoAgent browser connection timed out after ${DEFAULT_WS_CONNECT_TIMEOUT_MS}ms.`), + 'NeoAgent connection timeout handling failed', + ); + }, DEFAULT_WS_CONNECT_TIMEOUT_MS); - ws.addEventListener('open', () => { - if (socket !== ws) return; - clearTimeout(connectTimeout); - updateStatus('connected'); - }); - ws.addEventListener('close', () => { - clearTimeout(connectTimeout); - handleSocketDisconnected(ws).catch((error) => { - console.error('NeoAgent disconnect handling failed', error); + ws.addEventListener('open', () => { + if (socket !== ws || socketEpoch !== connectionEpoch) { + try { ws.close(); } catch {} + finish(reject, new Error('NeoAgent browser connection was superseded.')); + return; + } + reconnectAttempt = 0; + updateStatus('connected', socketEpoch).catch((error) => { + console.error('NeoAgent connected status update failed', error); + }); + finish(resolve, { connected: true }); }); - }); - ws.addEventListener('error', () => { - clearTimeout(connectTimeout); - handleSocketDisconnected(ws).catch((error) => { - console.error('NeoAgent socket error handling failed', error); + ws.addEventListener('close', () => { + disconnect( + new Error('NeoAgent browser connection closed before it was ready.'), + 'NeoAgent disconnect handling failed', + ); }); - }); - ws.addEventListener('message', (event) => { - handleSocketMessage(event.data).catch((error) => { - console.error('NeoAgent command handling failed', error); + ws.addEventListener('error', () => { + disconnect( + new Error('NeoAgent browser connection failed.'), + 'NeoAgent socket error handling failed', + ); + }); + ws.addEventListener('message', (event) => { + handleSocketMessage(ws, event.data).catch((error) => { + console.error('NeoAgent command handling failed', error); + }); }); }); + // The socket can fail while the storage status write is still pending. Attach + // a handler immediately; returning `ready` below still preserves rejection. + ready.catch(() => {}); - return { connected: false }; + await updateStatus('connecting', socketEpoch).catch((error) => { + console.error('NeoAgent connecting status update failed', error); + }); + return ready; } -async function handleSocketMessage(raw) { +function connect() { + if (socket?.readyState === WebSocket.OPEN) { + return Promise.resolve({ connected: true }); + } + if (connectPromise) return connectPromise; + const pending = connectOnce(); + connectPromise = pending; + pending.then( + () => { if (connectPromise === pending) connectPromise = null; }, + () => { if (connectPromise === pending) connectPromise = null; }, + ); + return pending; +} + +function sendSocketMessage(ws, message) { + if (socket !== ws || ws.readyState !== WebSocket.OPEN) return false; + try { + ws.send(JSON.stringify(message)); + return true; + } catch { + return false; + } +} + +async function handleSocketMessage(ws, raw) { let message; try { message = JSON.parse(raw); } catch { return; } - if (!message || message.type !== 'command' || !message.id) { + if (!message || !message.id) { return; } + if (message.type === MESSAGE_TYPES.URL_VALIDATION_RESULT) { + const pending = pendingUrlValidations.get(String(message.id)); + if (!pending || pending.socket !== ws) return; + pending.resolve( + Number(message.version) === EXTENSION_PROTOCOL_VERSION + && message.allowed === true, + ); + return; + } + if (message.type !== MESSAGE_TYPES.COMMAND) return; if (message.version != null && Number(message.version) !== EXTENSION_PROTOCOL_VERSION) { - socket?.send(JSON.stringify({ - type: 'result', + sendSocketMessage(ws, { + type: MESSAGE_TYPES.RESULT, version: EXTENSION_PROTOCOL_VERSION, id: message.id, ok: false, error: `Unsupported protocol version: ${message.version}`, - })); + }); return; } + + if (message.command === 'cancelCommand') { + const commandId = String(message.payload?.commandId || ''); + const controller = activeCommandControllers.get(commandId); + controller?.abort(new Error('Browser command cancelled by NeoAgent.')); + sendSocketMessage(ws, { + type: MESSAGE_TYPES.RESULT, + version: EXTENSION_PROTOCOL_VERSION, + id: message.id, + ok: true, + result: { success: Boolean(controller), commandId }, + }); + return; + } + + const controller = new AbortController(); + activeCommandControllers.set(message.id, controller); + const execute = () => protocol.run(message.command, message.payload || {}, { + signal: controller.signal, + }); + const queued = commandQueue.then(execute, execute); + commandQueue = queued.catch(() => {}); try { - const result = await protocol.run(message.command, message.payload || {}); - socket?.send(JSON.stringify({ type: 'result', version: EXTENSION_PROTOCOL_VERSION, id: message.id, ok: true, result })); + const result = await queued; + sendSocketMessage(ws, { + type: MESSAGE_TYPES.RESULT, + version: EXTENSION_PROTOCOL_VERSION, + id: message.id, + ok: true, + result, + }); } catch (error) { - socket?.send(JSON.stringify({ - type: 'result', + sendSocketMessage(ws, { + type: MESSAGE_TYPES.RESULT, version: EXTENSION_PROTOCOL_VERSION, id: message.id, ok: false, error: error?.message || String(error), - })); + }); + } finally { + if (activeCommandControllers.get(message.id) === controller) { + activeCommandControllers.delete(message.id); + } } } @@ -198,16 +407,25 @@ async function startPairing(serverUrl) { if (!normalized) throw new Error('NeoAgent server URL required.'); const { extensionName } = await getStorage(['extensionName']); const nameToUse = String(extensionName || 'Chrome Extension').trim() || 'Chrome Extension'; - const response = await fetchWithTimeout(`${normalized}/api/browser-extension/pairing/request`, { + const { response, payload } = await fetchJsonWithTimeout(`${normalized}/api/browser-extension/pairing/request`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ extensionName: nameToUse }), }); - const payload = await response.json().catch(() => ({})); if (!response.ok) throw new Error(payload.error || `Pairing failed: ${response.status}`); + if (!payload.pairingId || !payload.pairingSecret) { + throw new Error('NeoAgent server returned an incomplete pairing response.'); + } const approvalUrl = String(payload.approvalUrl || ''); const approvalParsed = (() => { try { return new URL(approvalUrl); } catch { return null; } })(); - if (!approvalParsed || !['http:', 'https:'].includes(approvalParsed.protocol)) { + const serverParsed = new URL(normalized); + if ( + !approvalParsed + || !['http:', 'https:'].includes(approvalParsed.protocol) + || approvalParsed.origin !== serverParsed.origin + || approvalParsed.username + || approvalParsed.password + ) { throw new Error('Invalid approval URL returned by server.'); } await setStorage({ @@ -226,14 +444,17 @@ async function claimPairing() { if (!serverUrl || !pairingId || !pairingSecret) { throw new Error('No pending pairing request.'); } + const normalizedServerUrl = normalizeServerUrl(serverUrl); const nameToUse = String(extensionName || 'Chrome Extension').trim() || 'Chrome Extension'; - const response = await fetchWithTimeout(`${serverUrl}/api/browser-extension/pairing/${encodeURIComponent(pairingId)}/claim`, { + const { response, payload } = await fetchJsonWithTimeout(`${normalizedServerUrl}/api/browser-extension/pairing/${encodeURIComponent(pairingId)}/claim`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ pairingSecret, extensionName: nameToUse }), }); - const payload = await response.json().catch(() => ({})); if (!response.ok) throw new Error(payload.error || `Claim failed: ${response.status}`); + if (!payload.token || !payload.tokenId) { + throw new Error('NeoAgent server returned an incomplete pairing claim.'); + } await setStorage({ token: payload.token, tokenId: payload.tokenId, @@ -246,20 +467,29 @@ async function claimPairing() { async function disconnect() { clearReconnectTimer(); - suppressSocketClose = true; - if (socket) { - try { socket.close(); } catch {} - } + reconnectAttempt = 0; + const disconnectedEpoch = ++connectionEpoch; + connectPromise = null; + const previousSocket = socket; socket = null; + if (previousSocket) { + try { previousSocket.close(); } catch {} + } + for (const controller of activeCommandControllers.values()) { + controller.abort(new Error('NeoAgent browser connection was disconnected.')); + } + activeCommandControllers.clear(); + rejectPendingUrlValidations(new Error('NeoAgent browser connection was disconnected.')); await removeStorage(['token', 'tokenId', 'pairingId', 'pairingSecret', 'approvalUrl']); - await updateStatus('not_paired'); + await updateStatus('not_paired', disconnectedEpoch); } async function checkForUpdates(preferredServerUrl) { const serverUrl = await resolveServerUrl(preferredServerUrl); if (!serverUrl) throw new Error('NeoAgent server URL required.'); - const response = await fetchWithTimeout(`${serverUrl}/api/browser-extension/latest`); - const latest = await response.json().catch(() => ({})); + const { response, payload: latest } = await fetchJsonWithTimeout( + `${serverUrl}/api/browser-extension/latest`, + ); if (!response.ok) throw new Error(latest.error || `Update check failed: ${response.status}`); const manifest = chrome.runtime.getManifest(); const currentVersion = manifest.version; diff --git a/extensions/chrome-browser/http.mjs b/extensions/chrome-browser/http.mjs new file mode 100644 index 00000000..f452429b --- /dev/null +++ b/extensions/chrome-browser/http.mjs @@ -0,0 +1,136 @@ +export const DEFAULT_EXTENSION_HTTP_TIMEOUT_MS = 10000; +export const DEFAULT_MAX_SERVER_RESPONSE_BYTES = 1024 * 1024; + +function abortError(signal, fallback = 'NeoAgent server request was aborted.') { + if (signal?.reason instanceof Error) return signal.reason; + const error = new Error(String(signal?.reason || fallback)); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +function throwIfAborted(signal) { + if (signal?.aborted) throw abortError(signal); +} + +function responseTooLargeError(maxResponseBytes) { + const error = new Error( + `NeoAgent server response exceeded the ${maxResponseBytes}-byte safety limit.`, + ); + error.code = 'EXTENSION_RESPONSE_TOO_LARGE'; + return error; +} + +function waitForAbortable(promise, signal) { + throwIfAborted(signal); + if (!signal) return Promise.resolve(promise); + return new Promise((resolve, reject) => { + const onAbort = () => reject(abortError(signal)); + signal.addEventListener('abort', onAbort, { once: true }); + Promise.resolve(promise).then(resolve, reject).finally(() => { + signal.removeEventListener('abort', onAbort); + }); + }); +} + +export async function readJsonResponse(response, options = {}) { + const signal = options.signal || null; + const maxResponseBytes = Math.max( + 1, + Number(options.maxResponseBytes) || DEFAULT_MAX_SERVER_RESPONSE_BYTES, + ); + throwIfAborted(signal); + const contentLength = Number(response?.headers?.get?.('content-length')); + if (Number.isFinite(contentLength) && contentLength > maxResponseBytes) { + await Promise.resolve(response?.body?.cancel?.()).catch(() => {}); + throw responseTooLargeError(maxResponseBytes); + } + + const reader = response?.body?.getReader?.(); + let text = ''; + if (!reader) { + text = await waitForAbortable(response.text(), signal); + if (new TextEncoder().encode(text).byteLength > maxResponseBytes) { + throw responseTooLargeError(maxResponseBytes); + } + } else { + const decoder = new TextDecoder(); + let totalBytes = 0; + const cancelReader = () => { + try { + Promise.resolve(reader.cancel(signal?.reason)).catch(() => {}); + } catch {} + }; + signal?.addEventListener('abort', cancelReader, { once: true }); + try { + while (true) { + const { done, value } = await waitForAbortable(reader.read(), signal); + if (done) break; + const chunk = value instanceof Uint8Array ? value : new Uint8Array(value || []); + totalBytes += chunk.byteLength; + if (totalBytes > maxResponseBytes) { + await Promise.resolve(reader.cancel()).catch(() => {}); + throw responseTooLargeError(maxResponseBytes); + } + text += decoder.decode(chunk, { stream: true }); + } + text += decoder.decode(); + } finally { + signal?.removeEventListener('abort', cancelReader); + reader.releaseLock?.(); + } + } + + if (!text.trim()) return {}; + try { + return JSON.parse(text); + } catch { + throw new Error('NeoAgent server returned invalid JSON.'); + } +} + +export async function fetchJsonWithTimeout(url, options = {}, config = {}) { + const timeoutMs = Math.max( + 1, + Number(config.timeoutMs) || DEFAULT_EXTENSION_HTTP_TIMEOUT_MS, + ); + const maxResponseBytes = Math.max( + 1, + Number(config.maxResponseBytes) || DEFAULT_MAX_SERVER_RESPONSE_BYTES, + ); + const fetchImpl = typeof config.fetchImpl === 'function' ? config.fetchImpl : fetch; + const callerSignal = options.signal || null; + throwIfAborted(callerSignal); + + const controller = new AbortController(); + const onCallerAbort = () => controller.abort(callerSignal.reason); + callerSignal?.addEventListener('abort', onCallerAbort, { once: true }); + const timer = setTimeout(() => { + const error = new Error(`NeoAgent server request timed out after ${timeoutMs}ms.`); + error.code = 'EXTENSION_HTTP_TIMEOUT'; + controller.abort(error); + }, timeoutMs); + + const { signal: _signal, ...fetchOptions } = options; + try { + const response = await waitForAbortable( + fetchImpl(url, { + ...fetchOptions, + signal: controller.signal, + }), + controller.signal, + ); + const payload = await readJsonResponse(response, { + signal: controller.signal, + maxResponseBytes, + }); + return { response, payload }; + } catch (error) { + if (callerSignal?.aborted) throw abortError(callerSignal); + if (controller.signal.aborted) throw abortError(controller.signal); + throw error; + } finally { + clearTimeout(timer); + callerSignal?.removeEventListener('abort', onCallerAbort); + } +} diff --git a/extensions/chrome-browser/protocol.mjs b/extensions/chrome-browser/protocol.mjs index f434b44a..06be6d82 100644 --- a/extensions/chrome-browser/protocol.mjs +++ b/extensions/chrome-browser/protocol.mjs @@ -1,3 +1,12 @@ +export const EXTENSION_PROTOCOL_VERSION = 1; + +export const MESSAGE_TYPES = Object.freeze({ + COMMAND: 'command', + RESULT: 'result', + URL_VALIDATION_REQUEST: 'urlValidationRequest', + URL_VALIDATION_RESULT: 'urlValidationResult', +}); + export const COMMANDS = Object.freeze({ LAUNCH: 'launch', NAVIGATE: 'navigate', @@ -13,8 +22,21 @@ export const COMMANDS = Object.freeze({ CLOSE: 'close', GET_PAGE_INFO: 'getPageInfo', GET_COOKIES: 'getCookies', + CANCEL_COMMAND: 'cancelCommand', }); +function abortError(signal) { + if (signal?.reason instanceof Error) return signal.reason; + const error = new Error(String(signal?.reason || 'Browser extension command aborted.')); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +function throwIfAborted(signal) { + if (signal?.aborted) throw abortError(signal); +} + function chromeCall(chromeApi, namespace, method, ...args) { return new Promise((resolve, reject) => { chromeApi[namespace][method](...args, (result) => { @@ -28,8 +50,50 @@ function chromeCall(chromeApi, namespace, method, ...args) { }); } -function delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); +function waitForAbortable(promise, signal = null) { + throwIfAborted(signal); + if (!signal) return promise; + return new Promise((resolve, reject) => { + let settled = false; + const finish = (callback, value) => { + if (settled) return; + settled = true; + signal.removeEventListener('abort', onAbort); + callback(value); + }; + const onAbort = () => finish(reject, abortError(signal)); + signal.addEventListener('abort', onAbort, { once: true }); + if (signal.aborted) onAbort(); + Promise.resolve(promise).then( + (value) => finish(resolve, value), + (error) => finish(reject, error), + ); + }); +} + +async function optionalResult(promise, fallback, signal = null) { + try { + return await promise; + } catch { + throwIfAborted(signal); + return fallback; + } +} + +function delay(ms, signal = null) { + throwIfAborted(signal); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject(abortError(signal)); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + if (signal?.aborted) onAbort(); + }); } function jsString(value) { @@ -58,113 +122,410 @@ function keyCodeFor(key) { return map[normalized] || (normalized.length === 1 ? normalized.toUpperCase().charCodeAt(0) : 0); } -export function createBrowserProtocol(chromeApi) { +function normalizePointCoordinate(value, label) { + if (value == null || value === '') { + throw new Error(`${label} coordinate is required.`); + } + const normalized = Number(value); + if (!Number.isFinite(normalized)) { + throw new Error(`${label} coordinate must be a finite number.`); + } + return Math.round(normalized); +} + +function ipv4Octets(hostname) { + if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname)) return null; + const parts = hostname.split('.').map(Number); + return parts.every((part) => part >= 0 && part <= 255) ? parts : null; +} + +export function isPrivateNetworkHostname(hostname) { + const normalized = String(hostname || '') + .toLowerCase() + .replace(/^\[|\]$/g, '') + .replace(/\.$/, ''); + if (!normalized) return true; + if ( + normalized === 'localhost' + || normalized === 'localhost.localdomain' + || normalized.endsWith('.localhost') + || normalized.endsWith('.local') + || normalized.endsWith('.internal') + ) { + return true; + } + + const octets = ipv4Octets(normalized); + if (octets) { + const [a, b, c] = octets; + return a === 0 + || a === 10 + || a === 127 + || a >= 224 + || (a === 100 && b >= 64 && b <= 127) + || (a === 169 && b === 254) + || (a === 172 && b >= 16 && b <= 31) + || (a === 192 && b === 168) + || (a === 192 && b === 0 && c === 0) + || (a === 192 && b === 0 && c === 2) + || (a === 198 && (b === 18 || b === 19)) + || (a === 198 && b === 51 && c === 100) + || (a === 203 && b === 0 && c === 113); + } + + if (normalized.includes(':')) { + if (normalized === '::' || normalized === '::1') return true; + if (normalized.startsWith('::ffff:')) return true; + const first = Number.parseInt(normalized.split(':').find(Boolean) || '0', 16); + if (!Number.isFinite(first)) return true; + if ((first & 0xfe00) === 0xfc00) return true; + if ((first & 0xffc0) === 0xfe80) return true; + if ((first & 0xffc0) === 0xfec0) return true; + if ((first & 0xff00) === 0xff00) return true; + if (normalized.startsWith('2001:db8:')) return true; + } + + return false; +} + +export function normalizeNetworkValidationUrl(value) { + const raw = String(value || ''); + if (!raw || raw.length > 8192) return null; + let parsed; + try { + parsed = new URL(raw); + } catch { + return null; + } + if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) return null; + if (parsed.username || parsed.password || isPrivateNetworkHostname(parsed.hostname)) return null; + const protocol = parsed.protocol === 'ws:' + ? 'http:' + : (parsed.protocol === 'wss:' ? 'https:' : parsed.protocol); + return `${protocol}//${parsed.host}/`; +} + +const PAGE_ACCESS_COMMANDS = new Set([ + COMMANDS.LAUNCH, + COMMANDS.CLICK, + COMMANDS.CLICK_POINT, + COMMANDS.TYPE, + COMMANDS.TYPE_TEXT, + COMMANDS.PRESS_KEY, + COMMANDS.SCROLL, + COMMANDS.EXTRACT, + COMMANDS.EVALUATE, + COMMANDS.SCREENSHOT, + COMMANDS.GET_PAGE_INFO, +]); + +export function createBrowserProtocol(chromeApi, options = {}) { let attachedTabId = null; let activeTabId = null; + const pausedRequests = new Map(); + const maxPausedRequests = Math.max(1, Math.min(Number(options.maxPausedRequests) || 256, 1024)); + const validateUrl = typeof options.validateUrl === 'function' + ? options.validateUrl + : async () => false; const debuggee = () => ({ tabId: activeTabId }); - async function ensureTab() { + const call = (signal, namespace, method, ...args) => waitForAbortable( + chromeCall(chromeApi, namespace, method, ...args), + signal, + ); + + function abortPausedRequests(tabId, reason) { + for (const entry of pausedRequests.values()) { + if (entry.tabId === tabId && !entry.controller.signal.aborted) { + entry.controller.abort(reason); + } + } + } + + async function sendToTab(tabId, method, params = {}, signal = null) { + try { + return await call(signal, 'debugger', 'sendCommand', { tabId }, method, params); + } catch (error) { + if ( + tabId === attachedTabId + && /not attached|no tab with given id|target closed/i.test(String(error?.message || '')) + ) { + attachedTabId = null; + } + throw error; + } + } + + async function isNetworkUrlAllowed(url, signal = null) { + throwIfAborted(signal); + const normalized = normalizeNetworkValidationUrl(url); + if (!normalized) return false; + const result = await validateUrl(normalized, { signal }); + throwIfAborted(signal); + return result === true || result?.allowed === true; + } + + async function handlePausedRequest(source, params) { + const tabId = source?.tabId; + const requestId = String(params?.requestId || ''); + if (tabId == null || tabId !== attachedTabId || !requestId) return; + if (pausedRequests.size >= maxPausedRequests) { + await sendToTab(tabId, 'Fetch.failRequest', { + requestId, + errorReason: 'BlockedByClient', + }).catch(() => {}); + return; + } + + const key = `${tabId}:${requestId}`; + const previous = pausedRequests.get(key); + previous?.controller.abort(new Error('Browser request interception was superseded.')); + const controller = new AbortController(); + pausedRequests.set(key, { tabId, controller }); + let allowed = false; + try { + allowed = await isNetworkUrlAllowed(params?.request?.url, controller.signal); + } catch { + allowed = false; + } + try { + await sendToTab( + tabId, + allowed ? 'Fetch.continueRequest' : 'Fetch.failRequest', + allowed + ? { requestId } + : { requestId, errorReason: 'BlockedByClient' }, + ); + } catch { + // Detach and tab-close races make the paused request disappear on their own. + } finally { + if (pausedRequests.get(key)?.controller === controller) pausedRequests.delete(key); + } + } + + chromeApi.debugger?.onEvent?.addListener?.((source, method, params) => { + if (method !== 'Fetch.requestPaused') return; + handlePausedRequest(source, params).catch(() => {}); + }); + chromeApi.debugger?.onDetach?.addListener?.((source) => { + if (source?.tabId === attachedTabId) { + abortPausedRequests(source.tabId, new Error('Browser debugger detached.')); + attachedTabId = null; + } + }); + chromeApi.tabs?.onRemoved?.addListener?.((tabId) => { + if (tabId === activeTabId) activeTabId = null; + if (tabId === attachedTabId) { + abortPausedRequests(tabId, new Error('Browser tab closed.')); + attachedTabId = null; + } + }); + + async function ensureTab(signal = null) { + throwIfAborted(signal); if (activeTabId != null) { try { - await chromeCall(chromeApi, 'tabs', 'get', activeTabId); + await call(signal, 'tabs', 'get', activeTabId); return activeTabId; } catch { + throwIfAborted(signal); activeTabId = null; } } - const tabs = await chromeCall(chromeApi, 'tabs', 'query', { active: true, currentWindow: true }); + const tabs = await call(signal, 'tabs', 'query', { active: true, currentWindow: true }); if (tabs && tabs[0]?.id != null) { activeTabId = tabs[0].id; return activeTabId; } - const tab = await chromeCall(chromeApi, 'tabs', 'create', { url: 'about:blank', active: true }); + const tab = await call(signal, 'tabs', 'create', { url: 'about:blank', active: true }); activeTabId = tab.id; return activeTabId; } - async function attach() { - await ensureTab(); + async function attach(signal = null) { + await ensureTab(signal); if (attachedTabId === activeTabId) return; if (attachedTabId != null) { - await chromeCall(chromeApi, 'debugger', 'detach', { tabId: attachedTabId }).catch(() => {}); + abortPausedRequests(attachedTabId, new Error('Browser control moved to another tab.')); + await optionalResult( + call(signal, 'debugger', 'detach', { tabId: attachedTabId }), + undefined, + signal, + ); } try { - await chromeCall(chromeApi, 'debugger', 'attach', debuggee(), '1.3'); + await call(signal, 'debugger', 'attach', debuggee(), '1.3'); } catch (error) { + throwIfAborted(signal); if (!/another debugger|already attached|debugger is already attached/i.test(error.message)) { throw error; } } attachedTabId = activeTabId; - await send('Page.enable').catch(() => {}); - await send('Runtime.enable').catch(() => {}); - await send('DOM.enable').catch(() => {}); + try { + await sendToTab(attachedTabId, 'Fetch.enable', { + patterns: [ + { urlPattern: 'http://*/*', requestStage: 'Request' }, + { urlPattern: 'https://*/*', requestStage: 'Request' }, + { urlPattern: 'ws://*/*', requestStage: 'Request' }, + { urlPattern: 'wss://*/*', requestStage: 'Request' }, + ], + }, signal); + await optionalResult(send('Page.enable', {}, signal), undefined, signal); + await optionalResult(send('Runtime.enable', {}, signal), undefined, signal); + await optionalResult(send('DOM.enable', {}, signal), undefined, signal); + } catch (error) { + const failedTabId = attachedTabId; + abortPausedRequests(failedTabId, error); + attachedTabId = null; + if (failedTabId != null) { + await chromeCall(chromeApi, 'debugger', 'detach', { tabId: failedTabId }).catch(() => {}); + } + throw error; + } } - async function send(method, params = {}) { - return chromeCall(chromeApi, 'debugger', 'sendCommand', debuggee(), method, params); + async function send(method, params = {}, signal = null) { + const tabId = attachedTabId ?? activeTabId; + if (tabId == null) throw new Error('No browser tab is available.'); + return sendToTab(tabId, method, params, signal); } - async function evalJs(expression, options = {}) { - await attach(); + async function evalJs(expression, options = {}, signal = null) { + await attach(signal); const response = await send('Runtime.evaluate', { expression, awaitPromise: options.awaitPromise !== false, returnByValue: true, - }); + }, signal); if (response?.exceptionDetails) { throw new Error(response.exceptionDetails.text || 'JavaScript evaluation failed.'); } return response?.result?.value; } - async function waitForLoad(timeoutMs = 30000) { + async function waitForLoad(timeoutMs = 30000, signal = null) { const started = Date.now(); while (Date.now() - started < timeoutMs) { - const ready = await evalJs('document.readyState === "complete" || document.readyState === "interactive"', { awaitPromise: false }) - .catch(() => false); + throwIfAborted(signal); + const ready = await optionalResult( + evalJs( + 'document.readyState === "complete" || document.readyState === "interactive"', + { awaitPromise: false }, + signal, + ), + false, + signal, + ); if (ready) return; - await delay(250); + await delay(250, signal); } + throw new Error(`Page did not finish loading within ${timeoutMs}ms.`); } - async function waitForSelector(selector, timeoutMs = 10000) { + async function markCurrentDocument(signal = null) { + const key = `__neoagent_document_${crypto.randomUUID().replace(/-/g, '')}`; + const token = crypto.randomUUID(); + const marked = await optionalResult( + evalJs( + `globalThis[${jsString(key)}] = ${jsString(token)}; true`, + { awaitPromise: false }, + signal, + ), + false, + signal, + ); + return marked ? { key, token } : null; + } + + async function waitForDocumentReplacement(marker, timeoutMs = 30000, signal = null) { + if (!marker) { + await delay(250, signal); + return; + } + const started = Date.now(); + const expression = `globalThis[${jsString(marker.key)}] !== ${jsString(marker.token)}`; + while (Date.now() - started < timeoutMs) { + throwIfAborted(signal); + const replaced = await optionalResult( + evalJs(expression, { awaitPromise: false }, signal), + false, + signal, + ); + if (replaced) return; + await delay(100, signal); + } + throw new Error(`Page navigation did not commit within ${timeoutMs}ms.`); + } + + async function assertNavigationSucceeded(signal = null) { + const frameTree = await send('Page.getFrameTree', {}, signal); + const unreachableUrl = String(frameTree?.frameTree?.frame?.unreachableUrl || ''); + if (unreachableUrl) { + throw new Error('Browser navigation failed before the destination loaded.'); + } + await assertCurrentPageAllowed(signal); + } + + async function waitForSelector(selector, timeoutMs = 10000, signal = null) { if (!selector) return; const expression = `Boolean(document.querySelector(${jsString(selector)}))`; const started = Date.now(); while (Date.now() - started < timeoutMs) { - if (await evalJs(expression, { awaitPromise: false }).catch(() => false)) return; - await delay(200); + throwIfAborted(signal); + if (await optionalResult(evalJs(expression, { awaitPromise: false }, signal), false, signal)) return; + await delay(200, signal); } + throw new Error(`Element not found within ${timeoutMs}ms: ${selector}`); } - async function currentTab() { - await ensureTab(); - return chromeCall(chromeApi, 'tabs', 'get', activeTabId); + async function currentTab(signal = null) { + await ensureTab(signal); + return call(signal, 'tabs', 'get', activeTabId); } - async function screenshotDataUrl(options = {}) { - await attach(); + async function assertCurrentPageAllowed(signal = null) { + const tab = await currentTab(signal); + const url = String(tab?.url || ''); + if (url === 'about:blank') return tab; + if (!await isNetworkUrlAllowed(url, signal)) { + throw new Error('The current browser page is not permitted.'); + } + return tab; + } + + async function screenshotDataUrl(options = {}, signal = null) { + throwIfAborted(signal); + await attach(signal); const capture = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: options.fullPage === true, fromSurface: true, - }); - return `data:image/png;base64,${capture?.data || ''}`; + }, signal); + const encoded = String(capture?.data || ''); + if (encoded.length > 24 * 1024 * 1024) { + throw new Error('Browser screenshot is too large to transfer. Try a viewport screenshot instead.'); + } + return `data:image/png;base64,${encoded}`; } - async function pageSnapshot(options = {}) { - const tab = await currentTab(); - const title = await evalJs('document.title || ""', { awaitPromise: false }).catch(() => tab.title || ''); - const bodyText = await evalJs(`(() => { + async function pageSnapshot(options = {}, signal = null) { + throwIfAborted(signal); + const tab = await currentTab(signal); + const title = await optionalResult( + evalJs('document.title || ""', { awaitPromise: false }, signal), + tab.title || '', + signal, + ); + const bodyText = await optionalResult(evalJs(`(() => { const body = document.body; if (!body) return ''; const clone = body.cloneNode(true); clone.querySelectorAll('script, style, noscript').forEach((node) => node.remove()); return String(clone.innerText || '').slice(0, 10000); - })()`).catch(() => ''); + })()`, {}, signal), '', signal); const result = { title: title || tab.title || '', url: tab.url || '', @@ -172,12 +533,13 @@ export function createBrowserProtocol(chromeApi) { bodyText, }; if (options.screenshot !== false) { - result.screenshotDataUrl = await screenshotDataUrl(options); + result.screenshotDataUrl = await screenshotDataUrl(options, signal); } return result; } - async function locateTarget(payload = {}) { + async function locateTarget(payload = {}, signal = null) { + throwIfAborted(signal); const selector = String(payload.selector || '').trim(); const text = String(payload.text || '').trim().toLowerCase(); const expression = selector @@ -194,42 +556,50 @@ export function createBrowserProtocol(chromeApi) { const rect = target.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; })()`; - const point = await evalJs(expression); + const point = await evalJs(expression, {}, signal); if (!point || !Number.isFinite(Number(point.x)) || !Number.isFinite(Number(point.y))) { throw new Error(selector ? `Element not found: ${selector}` : `No clickable element found with text: ${payload.text}`); } return { x: Math.round(point.x), y: Math.round(point.y) }; } - async function clickPoint(x, y) { - await attach(); - const px = Math.max(0, Math.round(Number(x) || 0)); - const py = Math.max(0, Math.round(Number(y) || 0)); - await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: px, y: py }); - await send('Input.dispatchMouseEvent', { type: 'mousePressed', x: px, y: py, button: 'left', clickCount: 1 }); - await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: px, y: py, button: 'left', clickCount: 1 }); - await delay(500); + async function clickPoint(x, y, signal = null) { + throwIfAborted(signal); + await attach(signal); + const px = Math.max(0, normalizePointCoordinate(x, 'x')); + const py = Math.max(0, normalizePointCoordinate(y, 'y')); + await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: px, y: py }, signal); + await send('Input.dispatchMouseEvent', { type: 'mousePressed', x: px, y: py, button: 'left', clickCount: 1 }, signal); + await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: px, y: py, button: 'left', clickCount: 1 }, signal); + await delay(500, signal); return { x: px, y: py }; } - async function typeKey(key) { - await attach(); + async function typeKey(key, signal = null) { + await attach(signal); const normalized = String(key || '').trim(); const code = keyCodeFor(normalized); - await send('Input.dispatchKeyEvent', { type: 'keyDown', key: normalized, windowsVirtualKeyCode: code }); - await send('Input.dispatchKeyEvent', { type: 'keyUp', key: normalized, windowsVirtualKeyCode: code }); + await send('Input.dispatchKeyEvent', { type: 'keyDown', key: normalized, windowsVirtualKeyCode: code }, signal); + await send('Input.dispatchKeyEvent', { type: 'keyUp', key: normalized, windowsVirtualKeyCode: code }, signal); } - async function run(command, payload = {}) { + async function run(command, payload = {}, options = {}) { + const signal = options.signal || null; + throwIfAborted(signal); + if (PAGE_ACCESS_COMMANDS.has(command)) { + await assertCurrentPageAllowed(signal); + } switch (command) { case COMMANDS.GET_COOKIES: { const domains = Array.isArray(payload.domains) ? payload.domains.map((item) => String(item || '').replace(/^\./, '').toLowerCase()).filter(Boolean) : []; if (domains.length === 0) throw new Error('At least one cookie domain is required.'); + if (domains.length > 50) throw new Error('At most 50 cookie domains may be requested.'); const all = []; for (const domain of domains) { - const cookies = await chromeCall(chromeApi, 'cookies', 'getAll', { domain }); + throwIfAborted(signal); + const cookies = await call(signal, 'cookies', 'getAll', { domain }); all.push(...(Array.isArray(cookies) ? cookies : [])); } const seen = new Set(); @@ -258,50 +628,84 @@ export function createBrowserProtocol(chromeApi) { }; } case COMMANDS.LAUNCH: - await attach(); - return pageSnapshot({ screenshot: false }); + await attach(signal); + return pageSnapshot({ screenshot: false }, signal); case COMMANDS.NAVIGATE: - await attach(); if (!payload.url) throw new Error('url required'); - await send('Page.navigate', { url: String(payload.url) }); - await waitForLoad(); - await waitForSelector(payload.waitFor); - return pageSnapshot(payload); + if (!await isNetworkUrlAllowed(payload.url, signal)) { + throw new Error('This browser URL is not permitted.'); + } + await attach(signal); + { + const marker = await markCurrentDocument(signal); + let completed = false; + try { + const navigation = await send('Page.navigate', { url: String(payload.url) }, signal); + if (navigation?.errorText) { + throw new Error(`Browser navigation failed: ${navigation.errorText}`); + } + if (navigation?.isDownload === true) { + throw new Error('Browser navigation started a download instead of loading a page.'); + } + if (navigation?.loaderId) { + await waitForDocumentReplacement(marker, 30000, signal); + } else { + await delay(100, signal); + } + await waitForLoad(30000, signal); + await assertNavigationSucceeded(signal); + await waitForSelector(payload.waitFor, 10000, signal); + throwIfAborted(signal); + completed = true; + return pageSnapshot(payload, signal); + } finally { + if (!completed) await send('Page.stopLoading').catch(() => {}); + } + } case COMMANDS.CLICK: { - const point = await locateTarget(payload); - await clickPoint(point.x, point.y); - return pageSnapshot({ screenshot: payload.screenshot !== false }); + const point = await locateTarget(payload, signal); + await clickPoint(point.x, point.y, signal); + throwIfAborted(signal); + return pageSnapshot({ screenshot: payload.screenshot !== false }, signal); } case COMMANDS.CLICK_POINT: - await clickPoint(payload.x, payload.y); - return pageSnapshot({ screenshot: payload.screenshot !== false }); + await clickPoint(payload.x, payload.y, signal); + throwIfAborted(signal); + return pageSnapshot({ screenshot: payload.screenshot !== false }, signal); case COMMANDS.TYPE: if (!payload.selector) throw new Error('selector required'); if (payload.clear !== false) { await evalJs(`(() => { const el = document.querySelector(${jsString(payload.selector)}); - if (!el) throw new Error('Element not found: ${String(payload.selector).replace(/'/g, "\\'")}'); + if (!el) throw new Error(${jsString(`Element not found: ${payload.selector}`)}); el.focus(); if ('value' in el) el.value = ''; - })()`); + })()`, {}, signal); } else { - await evalJs(`document.querySelector(${jsString(payload.selector)})?.focus()`); + await evalJs(`document.querySelector(${jsString(payload.selector)})?.focus()`, {}, signal); } - await send('Input.insertText', { text: String(payload.text || '') }); - if (payload.pressEnter) await typeKey('Enter'); - return pageSnapshot({ screenshot: payload.screenshot !== false }); + await send('Input.insertText', { text: String(payload.text || '') }, signal); + throwIfAborted(signal); + if (payload.pressEnter) await typeKey('Enter', signal); + return pageSnapshot({ screenshot: payload.screenshot !== false }, signal); case COMMANDS.TYPE_TEXT: - await attach(); - await send('Input.insertText', { text: String(payload.text || '') }); - if (payload.pressEnter) await typeKey('Enter'); - return pageSnapshot({ screenshot: payload.screenshot !== false }); + await attach(signal); + await send('Input.insertText', { text: String(payload.text || '') }, signal); + throwIfAborted(signal); + if (payload.pressEnter) await typeKey('Enter', signal); + return pageSnapshot({ screenshot: payload.screenshot !== false }, signal); case COMMANDS.PRESS_KEY: - await typeKey(payload.key); - return pageSnapshot({ screenshot: payload.screenshot !== false }); + await typeKey(payload.key, signal); + throwIfAborted(signal); + return pageSnapshot({ screenshot: payload.screenshot !== false }, signal); case COMMANDS.SCROLL: - await evalJs(`window.scrollBy(${Math.round(Number(payload.deltaX) || 0)}, ${Math.round(Number(payload.deltaY) || 0)})`); - await delay(250); - return pageSnapshot({ screenshot: payload.screenshot !== false }); + await evalJs( + `window.scrollBy(${Math.round(Number(payload.deltaX) || 0)}, ${Math.round(Number(payload.deltaY) || 0)})`, + {}, + signal, + ); + await delay(250, signal); + return pageSnapshot({ screenshot: payload.screenshot !== false }, signal); case COMMANDS.EXTRACT: { const selector = payload.selector || 'body'; const attribute = payload.attribute || ''; @@ -314,24 +718,40 @@ export function createBrowserProtocol(chromeApi) { return el.innerText || ''; }; const els = Array.from(document.querySelectorAll(${jsString(selector)})); - if (${payload.all === true}) return { results: els.slice(0, 100).map(read) }; + if (${payload.all === true}) { + return { results: els.slice(0, 100).map((el) => String(read(el)).slice(0, 50000)) }; + } return { result: els[0] ? String(read(els[0])).slice(0, 50000) : '' }; })()`; - return evalJs(expression); + return evalJs(expression, {}, signal); } case COMMANDS.EVALUATE: { - const value = await evalJs(buildIsolatedEvaluationExpression(payload.script)); - return { result: typeof value === 'object' ? JSON.stringify(value) : String(value) }; + if (String(payload.script || '').length > 10000) { + throw new Error('script exceeds maximum length (10000)'); + } + const value = await evalJs(buildIsolatedEvaluationExpression(payload.script), {}, signal); + throwIfAborted(signal); + const serialized = typeof value === 'object' ? JSON.stringify(value) : String(value); + const maxChars = 1024 * 1024; + return { + result: String(serialized ?? '').slice(0, maxChars), + truncated: String(serialized ?? '').length > maxChars, + }; } case COMMANDS.SCREENSHOT: - return { screenshotDataUrl: await screenshotDataUrl(payload), fullPage: payload.fullPage === true }; + return { screenshotDataUrl: await screenshotDataUrl(payload, signal), fullPage: payload.fullPage === true }; case COMMANDS.GET_PAGE_INFO: { - const tab = await currentTab(); + const tab = await currentTab(signal); return { url: tab.url || null, title: tab.title || null }; } case COMMANDS.CLOSE: if (attachedTabId != null) { - await chromeCall(chromeApi, 'debugger', 'detach', { tabId: attachedTabId }).catch(() => {}); + abortPausedRequests(attachedTabId, new Error('Browser control closed.')); + await optionalResult( + call(signal, 'debugger', 'detach', { tabId: attachedTabId }), + undefined, + signal, + ); } attachedTabId = null; return { success: true }; @@ -347,6 +767,8 @@ export function createBrowserProtocol(chromeApi) { attach, send, evalJs, + handlePausedRequest, + isNetworkUrlAllowed, }, }; } diff --git a/flutter_app/lib/main_chat.dart b/flutter_app/lib/main_chat.dart index 640e9305..c290c0c8 100644 --- a/flutter_app/lib/main_chat.dart +++ b/flutter_app/lib/main_chat.dart @@ -436,7 +436,9 @@ class _ChatPanelState extends State with WidgetsBindingObserver { double? prevExtent; void settleInitial(int framesLeft) { WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || generation != _scrollGeneration || !_scrollController.hasClients) { + if (!mounted || + generation != _scrollGeneration || + !_scrollController.hasClients) { return; } final pos = _scrollController.position; @@ -463,6 +465,7 @@ class _ChatPanelState extends State with WidgetsBindingObserver { settleInitial(framesLeft - 1); }); } + settleInitial(120); return; } @@ -511,7 +514,8 @@ class _ChatPanelState extends State with WidgetsBindingObserver { if (messages.isEmpty) { // Agent switch resets the settle tracker so the next batch triggers hiding. _visibleMessageCountAtLastSettle = 0; - } else if (_visibleMessageCountAtLastSettle == 0 && !_awaitingInitialScrollSettle) { + } else if (_visibleMessageCountAtLastSettle == 0 && + !_awaitingInitialScrollSettle) { // Messages just went from 0 → N: hide until the scroll position settles. _awaitingInitialScrollSettle = true; } @@ -2039,7 +2043,11 @@ class _IgnoredChatsPanel extends StatelessWidget { const SizedBox(height: 3), Text( 'These channels are permanently silenced. To receive messages from them, add them manually to the access policy for the relevant platform.', - style: TextStyle(color: _textSecondary, fontSize: 13, height: 1.4), + style: TextStyle( + color: _textSecondary, + fontSize: 13, + height: 1.4, + ), ), ], ), @@ -2049,50 +2057,58 @@ class _IgnoredChatsPanel extends StatelessWidget { ), const SizedBox(height: 14), for (final key in ignored) - Builder(builder: (context) { - final sep = key.indexOf(':'); - final platform = sep > 0 ? key.substring(0, sep) : key; - final chatId = sep > 0 ? key.substring(sep + 1) : ''; - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: _bgSecondary, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: _borderLight), - ), - child: Row( - children: [ - Icon(Icons.block_rounded, size: 16, color: _textMuted), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - platform.toUpperCase(), - style: TextStyle( - color: _textMuted, - fontSize: 10, - fontWeight: FontWeight.w700, - letterSpacing: 0.6, + Builder( + builder: (context) { + final sep = key.indexOf(':'); + final platform = sep > 0 ? key.substring(0, sep) : key; + final chatId = sep > 0 ? key.substring(sep + 1) : ''; + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + decoration: BoxDecoration( + color: _bgSecondary, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: _borderLight), + ), + child: Row( + children: [ + Icon(Icons.block_rounded, size: 16, color: _textMuted), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + platform.toUpperCase(), + style: TextStyle( + color: _textMuted, + fontSize: 10, + fontWeight: FontWeight.w700, + letterSpacing: 0.6, + ), ), - ), - Text( - chatId.isNotEmpty ? chatId : platform, - style: TextStyle(color: _textPrimary, fontSize: 13), - ), - ], + Text( + chatId.isNotEmpty ? chatId : platform, + style: TextStyle( + color: _textPrimary, + fontSize: 13, + ), + ), + ], + ), ), - ), - TextButton( - onPressed: () => controller.removeIgnoredChat(key), - child: Text('Remove'), - ), - ], - ), - ); - }), + TextButton( + onPressed: () => controller.removeIgnoredChat(key), + child: Text('Remove'), + ), + ], + ), + ); + }, + ), ], ), ); @@ -2236,7 +2252,9 @@ class _ConnectionReconnectingBanner extends StatelessWidget { @override Widget build(BuildContext context) { - final msg = hasNetwork ? 'Reconnecting to server…' : 'No network connection'; + final msg = hasNetwork + ? 'Reconnecting to server…' + : 'No network connection'; return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( @@ -2308,7 +2326,6 @@ class _PendingApprovalBannerState extends State<_PendingApprovalBanner> { int _remaining() { final expiry = widget.approval.expiresAt; - if (expiry == null) return 30; return expiry.difference(DateTime.now()).inSeconds.clamp(0, 300); } @@ -2340,11 +2357,7 @@ class _PendingApprovalBannerState extends State<_PendingApprovalBanner> { color: _accent.withValues(alpha: 0.16), shape: BoxShape.circle, ), - child: Icon( - Icons.security_outlined, - size: 13, - color: _accent, - ), + child: Icon(Icons.security_outlined, size: 13, color: _accent), ), ), const SizedBox(width: 10), @@ -3685,61 +3698,6 @@ class _MessagingMiniPill extends StatelessWidget { } } -class _RunsMetricsStrip extends StatelessWidget { - const _RunsMetricsStrip({required this.runs, required this.totalLoaded}); - - final List runs; - final int totalLoaded; - - @override - Widget build(BuildContext context) { - final running = runs.where((run) => run.status == 'running').length; - final failed = runs.where((run) => run.isFailure).length; - final completed = runs.where((run) => run.status == 'completed').length; - final tokens = runs.fold(0, (sum, run) => sum + run.totalTokens); - - return Wrap( - spacing: 12, - runSpacing: 12, - children: [ - _RunMetricCard( - title: 'Showing', - value: '${runs.length}', - helper: totalLoaded == runs.length - ? 'Recent runs loaded' - : 'Filtered from $totalLoaded loaded runs', - color: _info, - ), - _RunMetricCard( - title: 'Completed', - value: '$completed', - helper: 'Finished successfully', - color: _success, - ), - _RunMetricCard( - title: 'Failed', - value: '$failed', - helper: 'Need attention', - color: _danger, - ), - _RunMetricCard( - title: 'Tokens', - value: _formatNumber(tokens), - helper: 'Across visible runs', - color: _accentHover, - ), - if (running > 0) - _RunMetricCard( - title: 'Running', - value: '$running', - helper: 'Still in progress', - color: _warning, - ), - ], - ); - } -} - class _RunMetricCard extends StatelessWidget { const _RunMetricCard({ required this.title, @@ -3787,323 +3745,6 @@ class _RunMetricCard extends StatelessWidget { } } -class _RunsFilterBar extends StatelessWidget { - const _RunsFilterBar({ - required this.searchController, - required this.statusFilter, - required this.onStatusChanged, - }); - - final TextEditingController searchController; - final String statusFilter; - final ValueChanged onStatusChanged; - - @override - Widget build(BuildContext context) { - const filters = ['all', 'running', 'completed', 'failed']; - return Card( - child: Padding( - padding: const EdgeInsets.all(18), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const _SectionTitle('Filter Runs'), - const SizedBox(height: 12), - TextField( - controller: searchController, - decoration: InputDecoration( - prefixIcon: Icon(Icons.search), - hintText: 'Search title, model, trigger, error, or run id', - suffixIcon: searchController.text.trim().isEmpty - ? null - : IconButton( - tooltip: 'Clear search', - onPressed: searchController.clear, - icon: Icon(Icons.close), - ), - ), - ), - const SizedBox(height: 14), - Wrap( - spacing: 10, - runSpacing: 10, - children: filters.map((filter) { - return FilterChip( - label: Text(_titleCase(filter)), - selected: statusFilter == filter, - selectedColor: _accentMuted, - checkmarkColor: _accent, - backgroundColor: _bgSecondary, - side: BorderSide(color: _border), - onSelected: (_) => onStatusChanged(filter), - ); - }).toList(), - ), - ], - ), - ), - ); - } -} - -class _RunsHistoryPane extends StatelessWidget { - const _RunsHistoryPane({ - required this.runs, - required this.selectedRunId, - required this.onSelect, - }); - - final List runs; - final String? selectedRunId; - final ValueChanged onSelect; - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(18), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded(child: _SectionTitle('Run History')), - Text( - '${runs.length} items', - style: TextStyle(color: _textSecondary), - ), - ], - ), - const SizedBox(height: 12), - ...runs.map((run) { - return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: _RunHistoryRow( - run: run, - selected: run.id == selectedRunId, - onTap: () => onSelect(run.id), - ), - ); - }), - ], - ), - ), - ); - } -} - -class _RunHistoryRow extends StatelessWidget { - const _RunHistoryRow({ - required this.run, - required this.selected, - required this.onTap, - }); - - final RunSummary run; - final bool selected; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return InkWell( - borderRadius: BorderRadius.circular(16), - onTap: onTap, - child: Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: selected ? _accentMuted : _bgSecondary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: selected ? _accent : _border), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 12, - height: 12, - margin: const EdgeInsets.only(top: 5), - decoration: BoxDecoration( - color: run.statusColor, - shape: BoxShape.circle, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - run.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontWeight: FontWeight.w700, height: 1.2), - ), - const SizedBox(height: 6), - Text( - '${run.triggerLabel} • ${run.createdAtLabel}${run.durationLabel == 'In progress' ? '' : ' • ${run.durationLabel}'}', - style: TextStyle(color: _textSecondary, fontSize: 12), - ), - const SizedBox(height: 4), - Text( - '${run.modelLabel} • ${run.totalTokensLabel} tokens', - style: TextStyle(color: _textSecondary, fontSize: 12), - ), - if (run.deliverableType.trim().isNotEmpty) ...[ - const SizedBox(height: 4), - Text( - 'Deliverable • ${run.deliverableType.replaceAll('_', ' ')}', - style: TextStyle(color: _accent, fontSize: 12), - ), - ], - if (run.error.trim().isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - run.error, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: _danger, - fontSize: 12, - height: 1.4, - ), - ), - ], - ], - ), - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _StatusPill(label: run.statusLabel, color: run.statusColor), - const SizedBox(height: 12), - Icon( - Icons.chevron_right, - color: selected ? _textPrimary : _textSecondary, - ), - ], - ), - ], - ), - ), - ); - } -} - -class _RunDetailWorkspace extends StatelessWidget { - const _RunDetailWorkspace({ - required this.run, - required this.detail, - required this.errorMessage, - required this.loading, - required this.onDelete, - required this.onCopyResponse, - }); - - final RunSummary? run; - final RunDetailSnapshot? detail; - final String? errorMessage; - final bool loading; - final Future Function() onDelete; - final Future Function(String response) onCopyResponse; - - @override - Widget build(BuildContext context) { - if (run == null) { - return const _EmptyCard( - title: 'Select a run', - subtitle: 'Pick a run from the history list to inspect its steps.', - ); - } - - final selectedRun = run!; - final snapshot = detail; - return Column( - children: [ - _RunHeroCard(run: selectedRun, onDelete: onDelete), - const SizedBox(height: 16), - if (loading && snapshot == null) - Card( - child: Padding( - padding: EdgeInsets.all(24), - child: Row( - children: [ - SizedBox.square( - dimension: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ), - SizedBox(width: 12), - Text( - 'Loading run detail...', - style: TextStyle(color: _textSecondary), - ), - ], - ), - ), - ) - else if (errorMessage case final message?) ...[ - _InlineError(message: message), - const SizedBox(height: 16), - ] else if (snapshot != null) ...[ - Wrap( - spacing: 12, - runSpacing: 12, - children: [ - _RunMetricCard( - title: 'Steps', - value: '${snapshot.steps.length}', - helper: 'Recorded events', - color: _info, - ), - _RunMetricCard( - title: 'Completed tools', - value: '${snapshot.completedTools}', - helper: 'Successful tool calls', - color: _success, - ), - _RunMetricCard( - title: 'Failures', - value: '${snapshot.failedTools}', - helper: 'Tool errors', - color: _danger, - ), - _RunMetricCard( - title: 'Helpers', - value: '${snapshot.helperCount}', - helper: 'Subagents or helpers', - color: _accentHover, - ), - _RunMetricCard( - title: 'Trace events', - value: '${snapshot.events.length}', - helper: 'Structured run timeline', - color: _warning, - ), - ], - ), - const SizedBox(height: 16), - _RunResponseCard( - response: snapshot.response, - onCopy: () => onCopyResponse(snapshot.response), - ), - if (snapshot.run.deliverableType.trim().isNotEmpty) ...[ - const SizedBox(height: 16), - _DeliverableSummaryCard(run: snapshot.run), - ], - const SizedBox(height: 16), - _RunTimelineCard(steps: snapshot.steps, loading: loading), - const SizedBox(height: 16), - _RunEventTimelineCard(events: snapshot.events, loading: loading), - ] else - const _EmptyCard( - title: 'No detail available', - subtitle: 'This run does not have step detail yet.', - ), - ], - ); - } -} - class _RunHeroCard extends StatelessWidget { const _RunHeroCard({required this.run, required this.onDelete}); @@ -4165,22 +3806,13 @@ class _RunHeroCard extends StatelessWidget { spacing: 6, runSpacing: 6, children: [ - _MetaPill( - label: run.triggerLabel, - icon: Icons.bolt_outlined, - ), - _MetaPill( - label: run.modelLabel, - icon: Icons.memory_outlined, - ), + _MetaPill(label: run.triggerLabel, icon: Icons.bolt_outlined), + _MetaPill(label: run.modelLabel, icon: Icons.memory_outlined), _MetaPill( label: run.createdAtLabel, icon: Icons.schedule_outlined, ), - _MetaPill( - label: run.durationLabel, - icon: Icons.timer_outlined, - ), + _MetaPill(label: run.durationLabel, icon: Icons.timer_outlined), if (run.totalTokensLabel.isNotEmpty) _MetaPill( label: '${run.totalTokensLabel} tok', @@ -4357,259 +3989,6 @@ class _DeliverableSummaryCard extends StatelessWidget { } } -class _RunTimelineCard extends StatelessWidget { - const _RunTimelineCard({required this.steps, required this.loading}); - - final List steps; - final bool loading; - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(18), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded(child: _SectionTitle('Step Timeline')), - if (loading) - const SizedBox.square( - dimension: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ], - ), - const SizedBox(height: 12), - if (steps.isEmpty) - Text( - 'No run steps recorded yet.', - style: TextStyle(color: _textSecondary), - ) - else - ...steps.map((step) { - return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: _RunStepCard(step: step), - ); - }), - ], - ), - ), - ); - } -} - -class _RunEventTimelineCard extends StatelessWidget { - const _RunEventTimelineCard({required this.events, required this.loading}); - - final List events; - final bool loading; - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(18), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded(child: _SectionTitle('Execution Trace')), - if (loading) - const SizedBox.square( - dimension: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ], - ), - const SizedBox(height: 12), - if (events.isEmpty) - Text( - 'No structured run events recorded yet.', - style: TextStyle(color: _textSecondary), - ) - else - ...events.map((event) { - return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: _RunEventCard(event: event), - ); - }), - ], - ), - ), - ); - } -} - -class _RunEventCard extends StatelessWidget { - const _RunEventCard({required this.event}); - - final RunEventItem event; - - @override - Widget build(BuildContext context) { - final accent = event.isFailure ? _danger : _info; - return Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: _bgSecondary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: accent.withValues(alpha: 0.24)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 10, - height: 10, - decoration: BoxDecoration( - color: accent, - shape: BoxShape.circle, - ), - ), - const SizedBox(width: 10), - Expanded( - child: Text( - event.title, - style: const TextStyle(fontWeight: FontWeight.w700), - ), - ), - if (event.createdAtLabel.isNotEmpty) - Text( - event.createdAtLabel, - style: TextStyle(color: _textSecondary, fontSize: 12), - ), - ], - ), - if (event.detail.trim().isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - event.detail, - style: TextStyle(color: _textSecondary, height: 1.4), - ), - ], - ], - ), - ); - } -} - -class _RunStepCard extends StatelessWidget { - const _RunStepCard({required this.step}); - - final RunStepItem step; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Container( - decoration: BoxDecoration( - color: _bgSecondary, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: _border), - ), - child: Theme( - data: theme.copyWith(dividerColor: Colors.transparent), - child: ExpansionTile( - tilePadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - childrenPadding: const EdgeInsets.fromLTRB(14, 0, 14, 14), - initiallyExpanded: - step.status == 'failed' || step.status == 'running', - leading: Container( - width: 34, - height: 34, - decoration: BoxDecoration( - color: step.statusColor.withValues(alpha: 0.16), - shape: BoxShape.circle, - ), - child: Center( - child: Text( - '${step.displayIndex}', - style: TextStyle( - color: step.statusColor, - fontWeight: FontWeight.w800, - ), - ), - ), - ), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(step.label, style: TextStyle(fontWeight: FontWeight.w700)), - const SizedBox(height: 6), - Text( - step.summary, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: _textSecondary, - fontSize: 12, - height: 1.45, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - _StatusPill(label: step.statusLabel, color: step.statusColor), - _MetaPill(label: step.typeLabel, icon: Icons.layers_outlined), - if (step.startedAt != null) - _MetaPill( - label: step.startedAtLabel!, - icon: Icons.schedule_outlined, - ), - if (step.durationLabel != null) - _MetaPill( - label: step.durationLabel!, - icon: Icons.timer_outlined, - ), - if (step.tokensUsed > 0) - _MetaPill( - label: '${_formatNumber(step.tokensUsed)} tokens', - icon: Icons.toll_outlined, - ), - ], - ), - ], - ), - children: [ - if (step.description.trim().isNotEmpty && - step.description.trim() != step.summary.trim()) - _RunDetailBlock(label: 'Description', value: step.description), - if (step.inputSummary.trim().isNotEmpty) - _RunDetailBlock(label: 'Input summary', value: step.inputSummary), - if (step.toolInput.trim().isNotEmpty) - _RunDetailBlock( - label: 'Tool input', - value: _truncateRunText(step.toolInput), - monospace: true, - ), - if (step.error.trim().isNotEmpty) - _RunDetailBlock( - label: 'Error', - value: step.error, - monospace: true, - ) - else if (step.result.trim().isNotEmpty) - _RunDetailBlock( - label: 'Result', - value: _truncateRunText(step.result), - monospace: true, - ), - ], - ), - ), - ); - } -} - class _RunDetailBlock extends StatelessWidget { const _RunDetailBlock({ required this.label, @@ -4855,10 +4234,7 @@ class _RunGraphEdgePainter extends CustomPainter { ..strokeWidth = 1.8 ..strokeCap = StrokeCap.round; - final start = Offset( - from.x + _FlowNode.w, - from.y + _FlowNode.h / 2, - ); + final start = Offset(from.x + _FlowNode.w, from.y + _FlowNode.h / 2); final end = Offset(to.x, to.y + _FlowNode.h / 2); if (from.y != to.y) { @@ -4971,10 +4347,7 @@ class _FlowNodeWidget extends StatelessWidget { const SizedBox(width: 5), Text( isSpecial ? node.nodeType : node.status, - style: TextStyle( - color: _textSecondary, - fontSize: 11, - ), + style: TextStyle(color: _textSecondary, fontSize: 11), ), ], ), @@ -5093,10 +4466,7 @@ class _RunFlowGraphCanvasState extends State<_RunFlowGraphCanvas> { Expanded( child: Text( '$stepCount step${stepCount == 1 ? '' : 's'} · tap a node to inspect', - style: TextStyle( - color: _textSecondary, - fontSize: 12.5, - ), + style: TextStyle(color: _textSecondary, fontSize: 12.5), ), ), if (widget.loading) @@ -5116,8 +4486,14 @@ class _RunFlowGraphCanvasState extends State<_RunFlowGraphCanvas> { vertical: 6, ), ), - icon: const Icon(Icons.center_focus_strong_outlined, size: 15), - label: const Text('Reset view', style: TextStyle(fontSize: 12)), + icon: const Icon( + Icons.center_focus_strong_outlined, + size: 15, + ), + label: const Text( + 'Reset view', + style: TextStyle(fontSize: 12), + ), ), ], ), @@ -5155,7 +4531,9 @@ class _RunFlowGraphCanvasState extends State<_RunFlowGraphCanvas> { node: node, selected: node.id == widget.selectedNodeId, onTap: () => widget.onNodeSelected( - node.id == widget.selectedNodeId ? null : node.id, + node.id == widget.selectedNodeId + ? null + : node.id, ), ), ), @@ -5312,32 +4690,31 @@ class _RunSelectorRail extends StatelessWidget { style: TextStyle(color: _textSecondary, fontSize: 12), ), ) - else - ...[ - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: math.min(filteredRuns.length, 40), - separatorBuilder: (_, __) => const SizedBox(height: 6), - itemBuilder: (context, index) { - final run = filteredRuns[index]; - final isSelected = run.id == selectedRunId; - return _RunSelectorRow( - run: run, - selected: isSelected, - onTap: () => onSelect(run.id), - ); - }, - ), - if (filteredRuns.length > 40) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - 'Showing 40 of ${filteredRuns.length} — use search to narrow results', - style: TextStyle(color: _textSecondary, fontSize: 11), - ), + else ...[ + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: math.min(filteredRuns.length, 40), + separatorBuilder: (_, __) => const SizedBox(height: 6), + itemBuilder: (context, index) { + final run = filteredRuns[index]; + final isSelected = run.id == selectedRunId; + return _RunSelectorRow( + run: run, + selected: isSelected, + onTap: () => onSelect(run.id), + ); + }, + ), + if (filteredRuns.length > 40) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + 'Showing 40 of ${filteredRuns.length} — use search to narrow results', + style: TextStyle(color: _textSecondary, fontSize: 11), ), - ], + ), + ], ], ), ), @@ -5396,10 +4773,7 @@ class _RunSelectorRow extends StatelessWidget { const SizedBox(height: 3), Text( '${run.createdAtLabel} · ${run.durationLabel}', - style: TextStyle( - color: _textSecondary, - fontSize: 11, - ), + style: TextStyle(color: _textSecondary, fontSize: 11), ), ], ), @@ -5522,7 +4896,11 @@ class _RunSelectedStepCard extends StatelessWidget { color: color.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(10), ), - child: Icon(Icons.build_circle_outlined, size: 18, color: color), + child: Icon( + Icons.build_circle_outlined, + size: 18, + color: color, + ), ), const SizedBox(width: 10), Expanded( @@ -5539,10 +4917,7 @@ class _RunSelectedStepCard extends StatelessWidget { const SizedBox(height: 2), Text( step.typeLabel, - style: TextStyle( - color: _textSecondary, - fontSize: 12, - ), + style: TextStyle(color: _textSecondary, fontSize: 12), ), ], ), @@ -5583,7 +4958,11 @@ class _RunSelectedStepCard extends StatelessWidget { ], if (step.error.trim().isNotEmpty) ...[ const SizedBox(height: 8), - _RunDetailBlock(label: 'Error', value: step.error, monospace: true), + _RunDetailBlock( + label: 'Error', + value: step.error, + monospace: true, + ), ] else if (step.result.trim().isNotEmpty) ...[ const SizedBox(height: 8), _RunDetailBlock( diff --git a/flutter_app/lib/main_controller.dart b/flutter_app/lib/main_controller.dart index 81bf3d2b..9c938b62 100644 --- a/flutter_app/lib/main_controller.dart +++ b/flutter_app/lib/main_controller.dart @@ -5950,11 +5950,13 @@ class NeoAgentController extends ChangeNotifier { List get enabledModelIds { final raw = settings['enabled_models']; if (raw is List) { - final knownIds = supportedModels.map((model) => model.id).toSet(); - final filtered = raw - .map((item) => item.toString()) - .where((id) => knownIds.contains(id)) - .toList(); + final filtered = []; + for (final item in raw) { + final model = _modelForValue(item.toString(), supportedModels); + if (model != null && !filtered.contains(model.id)) { + filtered.add(model.id); + } + } if (filtered.isNotEmpty) { return filtered; } @@ -5965,18 +5967,30 @@ class NeoAgentController extends ChangeNotifier { .toList(); } - String get defaultChatModel => - settings['default_chat_model']?.toString() ?? 'auto'; + String get defaultChatModel => _ensureModelValue( + settings['default_chat_model']?.toString() ?? 'auto', + supportedModels, + allowAuto: true, + ); - String get defaultSubagentModel => - settings['default_subagent_model']?.toString() ?? 'auto'; + String get defaultSubagentModel => _ensureModelValue( + settings['default_subagent_model']?.toString() ?? 'auto', + supportedModels, + allowAuto: true, + ); - String get defaultSpeechModel => - settings['default_speech_model']?.toString() ?? 'auto'; + String get defaultSpeechModel => _ensureModelValue( + settings['default_speech_model']?.toString() ?? 'auto', + supportedModels, + allowAuto: true, + ); - String get fallbackModel => - settings['fallback_model_id']?.toString() ?? - _firstAvailableModelId(supportedModels); + String get fallbackModel => _ensureModelValue( + settings['fallback_model_id']?.toString() ?? + _firstAvailableModelId(supportedModels), + supportedModels, + allowAuto: false, + ); String get voiceSttProvider => _settingString('voice_stt_provider', 'openai', lowercase: true); @@ -6117,12 +6131,7 @@ class NeoAgentController extends ChangeNotifier { } ModelMeta? _modelById(String id) { - for (final model in supportedModels) { - if (model.id == id) { - return model; - } - } - return null; + return _modelForValue(id, supportedModels); } void _ensureUpdatePolling() { diff --git a/flutter_app/lib/main_models.dart b/flutter_app/lib/main_models.dart index 5a5b1935..314d0c7f 100644 --- a/flutter_app/lib/main_models.dart +++ b/flutter_app/lib/main_models.dart @@ -1852,6 +1852,7 @@ class AgentProfile { class ModelMeta { const ModelMeta({ required this.id, + required this.modelId, required this.label, required this.provider, required this.purpose, @@ -1864,6 +1865,7 @@ class ModelMeta { factory ModelMeta.fromJson(Map json) { return ModelMeta( id: json['id']?.toString() ?? '', + modelId: json['modelId']?.toString() ?? json['id']?.toString() ?? '', label: json['label']?.toString() ?? '', provider: json['provider']?.toString() ?? '', purpose: json['purpose']?.toString() ?? '', @@ -1875,6 +1877,7 @@ class ModelMeta { } final String id; + final String modelId; final String label; final String provider; final String purpose; diff --git a/flutter_app/lib/main_operations.dart b/flutter_app/lib/main_operations.dart index 91bf37e7..bacacd52 100644 --- a/flutter_app/lib/main_operations.dart +++ b/flutter_app/lib/main_operations.dart @@ -505,8 +505,10 @@ class _SkillsPanelState extends State // Installed tab search & filter state String _installedQuery = ''; - String _installedStatusFilter = 'all'; // 'all' | 'active' | 'draft' | 'disabled' - String _installedSourceFilter = 'all'; // 'all' | 'built-in' | 'learned' | 'user' | 'store' + String _installedStatusFilter = + 'all'; // 'all' | 'active' | 'draft' | 'disabled' + String _installedSourceFilter = + 'all'; // 'all' | 'built-in' | 'learned' | 'user' | 'store' late final TextEditingController _installedSearchController; @override @@ -645,18 +647,36 @@ class _SkillsPanelState extends State final q = _installedQuery; if (q.isNotEmpty && !skill.name.toLowerCase().contains(q) && - !skill.description.toLowerCase().contains(q)) return false; + !skill.description.toLowerCase().contains(q)) { + return false; + } if (_installedStatusFilter != 'all') { - if (_installedStatusFilter == 'active' && (!skill.enabled || skill.draft)) return false; - if (_installedStatusFilter == 'draft' && !skill.draft) return false; - if (_installedStatusFilter == 'disabled' && skill.enabled) return false; + if (_installedStatusFilter == 'active' && + (!skill.enabled || skill.draft)) { + return false; + } + if (_installedStatusFilter == 'draft' && !skill.draft) { + return false; + } + if (_installedStatusFilter == 'disabled' && skill.enabled) { + return false; + } + } + if (_installedSourceFilter != 'all' && + skill.source != _installedSourceFilter) { + return false; } - if (_installedSourceFilter != 'all' && skill.source != _installedSourceFilter) return false; return true; }).toList(); final statusFilters = ['all', 'active', 'draft', 'disabled']; - final sourceFilters = ['all', 'built-in', 'learned', 'user', 'store']; + final sourceFilters = [ + 'all', + 'built-in', + 'learned', + 'user', + 'store', + ]; return Card( child: Column( @@ -757,157 +777,174 @@ class _SkillsPanelState extends State separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) { final skill = filteredSkills[index]; - return LayoutBuilder( - builder: (context, constraints) { - final compact = constraints.maxWidth < 760; - return Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: _bgSecondary, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: _border), - ), - child: compact - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - skill.name, - style: TextStyle(fontWeight: FontWeight.w700), - ), - ), - Switch( - value: skill.enabled, - onChanged: (value) => controller - .setSkillEnabled(skill.name, value), - ), - ], - ), - Text( - skill.description.ifEmpty('No description'), - style: TextStyle(color: _textSecondary), - ), - const SizedBox(height: 10), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - _MetaPill( - label: skill.category, - icon: Icons.folder_outlined, - ), - _MetaPill( - label: skill.source, - icon: Icons.source_outlined, - ), - if (skill.draft) - const _MetaPill( - label: 'Draft', - icon: Icons.edit_note_outlined, - ), - ], - ), - const SizedBox(height: 10), - Row( - children: [ - const Spacer(), - OutlinedButton( - onPressed: () => - _openSkillEditor(context, skill.name), - child: Text('Open'), - ), - const SizedBox(width: 8), - TextButton.icon( - onPressed: () => - _confirmDeleteSkill(context, skill.name), - icon: Icon(Icons.delete_outline), - style: TextButton.styleFrom( - foregroundColor: _danger, - ), - label: Text('Delete'), - ), - ], - ), - ], - ) - : Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - skill.name, - style: TextStyle(fontWeight: FontWeight.w700), - ), - const SizedBox(height: 6), - Text( - skill.description.ifEmpty('No description'), - style: TextStyle(color: _textSecondary), - ), - const SizedBox(height: 10), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - _MetaPill( - label: skill.category, - icon: Icons.folder_outlined, - ), - _MetaPill( - label: skill.source, - icon: Icons.source_outlined, + return LayoutBuilder( + builder: (context, constraints) { + final compact = constraints.maxWidth < 760; + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: _bgSecondary, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: _border), + ), + child: compact + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + skill.name, + style: TextStyle( + fontWeight: FontWeight.w700, + ), + ), + ), + Switch( + value: skill.enabled, + onChanged: (value) => controller + .setSkillEnabled(skill.name, value), + ), + ], + ), + Text( + skill.description.ifEmpty('No description'), + style: TextStyle(color: _textSecondary), + ), + const SizedBox(height: 10), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _MetaPill( + label: skill.category, + icon: Icons.folder_outlined, + ), + _MetaPill( + label: skill.source, + icon: Icons.source_outlined, + ), + if (skill.draft) + const _MetaPill( + label: 'Draft', + icon: Icons.edit_note_outlined, + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + const Spacer(), + OutlinedButton( + onPressed: () => _openSkillEditor( + context, + skill.name, + ), + child: Text('Open'), + ), + const SizedBox(width: 8), + TextButton.icon( + onPressed: () => _confirmDeleteSkill( + context, + skill.name, + ), + icon: Icon(Icons.delete_outline), + style: TextButton.styleFrom( + foregroundColor: _danger, + ), + label: Text('Delete'), + ), + ], + ), + ], + ) + : Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + skill.name, + style: TextStyle( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 6), + Text( + skill.description.ifEmpty( + 'No description', + ), + style: TextStyle( + color: _textSecondary, + ), + ), + const SizedBox(height: 10), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _MetaPill( + label: skill.category, + icon: Icons.folder_outlined, + ), + _MetaPill( + label: skill.source, + icon: Icons.source_outlined, + ), + if (skill.draft) + const _MetaPill( + label: 'Draft', + icon: Icons.edit_note_outlined, + ), + ], + ), + ], ), - if (skill.draft) - const _MetaPill( - label: 'Draft', - icon: Icons.edit_note_outlined, + ), + const SizedBox(width: 10), + Column( + children: [ + Switch( + value: skill.enabled, + onChanged: (value) => controller + .setSkillEnabled(skill.name, value), ), - ], - ), - ], - ), - ), - const SizedBox(width: 10), - Column( - children: [ - Switch( - value: skill.enabled, - onChanged: (value) => controller - .setSkillEnabled(skill.name, value), - ), - OutlinedButton( - onPressed: () => - _openSkillEditor(context, skill.name), - child: Text('Open'), - ), - const SizedBox(height: 6), - TextButton.icon( - onPressed: () => - _confirmDeleteSkill(context, skill.name), - icon: Icon(Icons.delete_outline), - style: TextButton.styleFrom( - foregroundColor: _danger, - ), - label: Text('Delete'), + OutlinedButton( + onPressed: () => _openSkillEditor( + context, + skill.name, + ), + child: Text('Open'), + ), + const SizedBox(height: 6), + TextButton.icon( + onPressed: () => _confirmDeleteSkill( + context, + skill.name, + ), + icon: Icon(Icons.delete_outline), + style: TextButton.styleFrom( + foregroundColor: _danger, + ), + label: Text('Delete'), + ), + ], + ), + ], ), - ], - ), - ], - ), - ); - }, - ); + ); + }, + ); }, ), ), - ], - ), - ); + ], + ), + ); } Widget _buildStoreTab( @@ -1465,8 +1502,9 @@ class _MemoryPanelState extends State ); if (!mounted) return; _llmImportController.clear(); - final warningText = - result.warnings.isEmpty ? '' : ' ${result.warnings.join(' ')}'; + final warningText = result.warnings.isEmpty + ? '' + : ' ${result.warnings.join(' ')}'; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -1494,9 +1532,7 @@ class _MemoryPanelState extends State : controller.memories; if (_entityFilter == null) return base; return base - .where( - (m) => m.entities.any((e) => e.name == _entityFilter), - ) + .where((m) => m.entities.any((e) => e.name == _entityFilter)) .toList(); } @@ -1588,7 +1624,10 @@ class _MemoryPanelState extends State }); } - void _openRetrievalInspector(BuildContext context, NeoAgentController controller) { + void _openRetrievalInspector( + BuildContext context, + NeoAgentController controller, + ) { Navigator.of(context).push( MaterialPageRoute( builder: (context) => RetrievalInspectorView(controller: controller), @@ -1603,7 +1642,8 @@ class _MemoryPanelState extends State final memoriesToShow = _visibleMemories; final selectedIds = _selectedVisibleMemoryIds.toSet(); final selectedCount = selectedIds.length; - final allVisibleSelected = memoriesToShow.isNotEmpty && + final allVisibleSelected = + memoriesToShow.isNotEmpty && memoriesToShow.every((m) => selectedIds.contains(m.id)); final showingSearchResults = controller.memoryRecallResults.isNotEmpty; final compact = MediaQuery.sizeOf(context).width < 760; @@ -1644,9 +1684,7 @@ class _MemoryPanelState extends State padding: const EdgeInsets.all(18), child: Row( children: [ - _MemoryConfidenceGauge( - confidence: stats.averageConfidence, - ), + _MemoryConfidenceGauge(confidence: stats.averageConfidence), const SizedBox(width: 18), Expanded( child: Wrap( @@ -1705,9 +1743,7 @@ class _MemoryPanelState extends State children: [ Row( children: [ - Expanded( - child: const _SectionTitle('Knowledge Graph'), - ), + Expanded(child: const _SectionTitle('Knowledge Graph')), if (_entityFilter != null) TextButton.icon( onPressed: () => @@ -1720,10 +1756,7 @@ class _MemoryPanelState extends State const SizedBox(height: 4), Text( 'Tap an entity to filter memories by it.', - style: TextStyle( - color: _textSecondary, - fontSize: 12, - ), + style: TextStyle(color: _textSecondary, fontSize: 12), ), const SizedBox(height: 14), SizedBox( @@ -1857,15 +1890,16 @@ class _MemoryPanelState extends State Wrap( spacing: 8, runSpacing: 8, - crossAxisAlignment: - WrapCrossAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, children: [ OutlinedButton.icon( - onPressed: allVisibleSelected || + onPressed: + allVisibleSelected || _bulkActionInFlight ? null : () => _selectAllVisibleMemories( - memoriesToShow), + memoriesToShow, + ), icon: Icon( Icons.done_all_outlined, size: 16, @@ -1903,9 +1937,7 @@ class _MemoryPanelState extends State Icons.archive_outlined, size: 16, ), - label: Text( - 'Archive ($selectedCount)', - ), + label: Text('Archive ($selectedCount)'), ), OutlinedButton.icon( onPressed: _bulkActionInFlight @@ -1923,9 +1955,7 @@ class _MemoryPanelState extends State Icons.delete_sweep_outlined, size: 16, ), - label: Text( - 'Delete ($selectedCount)', - ), + label: Text('Delete ($selectedCount)'), ), ], ], @@ -1941,8 +1971,9 @@ class _MemoryPanelState extends State ) else ...memoriesToShow.map((memory) { - final isSelected = - selectedIds.contains(memory.id); + final isSelected = selectedIds.contains( + memory.id, + ); return _MemoryRow( memory: memory, isSelected: isSelected, @@ -1950,8 +1981,7 @@ class _MemoryPanelState extends State memory.id, !isSelected, ), - onCheck: (value) => - _toggleMemorySelection( + onCheck: (value) => _toggleMemorySelection( memory.id, value ?? false, ), @@ -1964,9 +1994,9 @@ class _MemoryPanelState extends State 'This memory will be removed permanently.', onConfirm: () => _deleteSingleMemory( - controller, - memory.id, - ), + controller, + memory.id, + ), ), ); }), @@ -1985,9 +2015,7 @@ class _MemoryPanelState extends State Expanded( child: Text( 'Key-value pairs that persist across conversations.', - style: TextStyle( - color: _textSecondary, - ), + style: TextStyle(color: _textSecondary), ), ), TextButton.icon( @@ -2001,75 +2029,75 @@ class _MemoryPanelState extends State ], ), const SizedBox(height: 10), - if (controller - .memoryOverview.coreEntries.isEmpty) + if (controller.memoryOverview.coreEntries.isEmpty) Text( 'No core memory entries yet.', style: TextStyle(color: _textSecondary), ) else - ...controller.memoryOverview.coreEntries - .entries + ...controller.memoryOverview.coreEntries.entries .map((entry) { - return Container( - width: double.infinity, - margin: const EdgeInsets.only(bottom: 10), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: _bgSecondary, - borderRadius: - BorderRadius.circular(12), - border: Border.all(color: _border), - ), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - entry.key, - style: TextStyle( - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 6), - Text( - entry.value.toString(), - ), - ], - ), + return Container( + width: double.infinity, + margin: const EdgeInsets.only( + bottom: 10, ), - IconButton( - onPressed: () => - _openCoreMemoryEditor( - context, - controller, - keyValue: entry, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: _bgSecondary, + borderRadius: BorderRadius.circular( + 12, ), - icon: Icon(Icons.edit_outlined), + border: Border.all(color: _border), ), - IconButton( - onPressed: () => _confirmDelete( - context, - title: - 'Delete core memory entry?', - message: - 'Remove "${entry.key}" from core memory.', - onConfirm: () => controller - .deleteCoreMemory( - entry.key, + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + entry.key, + style: TextStyle( + fontWeight: + FontWeight.w700, + ), + ), + const SizedBox(height: 6), + Text(entry.value.toString()), + ], + ), ), - ), - icon: Icon(Icons.delete_outline), + IconButton( + onPressed: () => + _openCoreMemoryEditor( + context, + controller, + keyValue: entry, + ), + icon: Icon(Icons.edit_outlined), + ), + IconButton( + onPressed: () => _confirmDelete( + context, + title: + 'Delete core memory entry?', + message: + 'Remove "${entry.key}" from core memory.', + onConfirm: () => + controller.deleteCoreMemory( + entry.key, + ), + ), + icon: Icon(Icons.delete_outline), + ), + ], ), - ], - ), - ); - }), + ); + }), ], ), ), @@ -2092,11 +2120,8 @@ class _MemoryPanelState extends State FilledButton.icon( onPressed: _llmPromptLoading ? null - : () => - _loadLlmPrompt(controller), - icon: Icon( - Icons.auto_awesome_outlined, - ), + : () => _loadLlmPrompt(controller), + icon: Icon(Icons.auto_awesome_outlined), label: Text( _llmPromptLoading ? 'Generating...' @@ -2105,11 +2130,9 @@ class _MemoryPanelState extends State ), OutlinedButton.icon( onPressed: - _llmPromptController.text - .trim() - .isEmpty - ? null - : _copyLlmPrompt, + _llmPromptController.text.trim().isEmpty + ? null + : _copyLlmPrompt, icon: Icon(Icons.copy_all_outlined), label: Text('Copy Prompt'), ), @@ -2122,8 +2145,7 @@ class _MemoryPanelState extends State maxLines: 8, readOnly: true, decoration: const InputDecoration( - labelText: - 'Prompt to paste into another AI', + labelText: 'Prompt to paste into another AI', ), ), const SizedBox(height: 16), @@ -2133,8 +2155,7 @@ class _MemoryPanelState extends State onChanged: _llmImporting ? null : (value) => setState( - () => - _llmApplyBehaviorNotes = value, + () => _llmApplyBehaviorNotes = value, ), title: Text('Apply behavior notes'), subtitle: Text( @@ -2147,8 +2168,7 @@ class _MemoryPanelState extends State onChanged: _llmImporting ? null : (value) => setState( - () => - _llmApplyCoreMemory = value, + () => _llmApplyCoreMemory = value, ), title: Text('Apply core memory'), subtitle: Text( @@ -2168,13 +2188,10 @@ class _MemoryPanelState extends State FilledButton.icon( onPressed: _llmImporting ? null - : () => - _importLlmMemories(controller), + : () => _importLlmMemories(controller), icon: Icon(Icons.file_download_outlined), label: Text( - _llmImporting - ? 'Importing...' - : 'Import', + _llmImporting ? 'Importing...' : 'Import', ), ), ], @@ -2453,9 +2470,10 @@ class _MemoryConfidenceGaugeState extends State<_MemoryConfidenceGauge> vsync: this, duration: const Duration(milliseconds: 1200), ); - _progress = Tween(begin: 0, end: widget.confidence).animate( - CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic), - ); + _progress = Tween( + begin: 0, + end: widget.confidence, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic)); _controller.forward(); } @@ -2463,12 +2481,10 @@ class _MemoryConfidenceGaugeState extends State<_MemoryConfidenceGauge> void didUpdateWidget(_MemoryConfidenceGauge oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.confidence != widget.confidence) { - _progress = Tween( - begin: _progress.value, - end: widget.confidence, - ).animate( - CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic), - ); + _progress = Tween(begin: _progress.value, end: widget.confidence) + .animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic), + ); _controller ..reset() ..forward(); @@ -2658,10 +2674,7 @@ class _MemoryRow extends StatelessWidget { if (onDelete != null) IconButton( onPressed: onDelete, - icon: Icon( - Icons.delete_outline, - size: 18, - ), + icon: Icon(Icons.delete_outline, size: 18), ), ], ), @@ -2686,10 +2699,7 @@ class _MemoryRow extends StatelessWidget { const SizedBox(height: 6), Text( memory.createdAtLabel, - style: TextStyle( - fontSize: 11, - color: _textMuted, - ), + style: TextStyle(fontSize: 11, color: _textMuted), ), ], ), @@ -2778,27 +2788,31 @@ class _EntityGraphViewState extends State<_EntityGraphView> for (int i = 0; i < entities.length; i++) { final entity = entities[i]; final sizeFactor = 0.4 + 0.6 * (entity.mentionCount / maxMention); - _nodes.add(_GraphNode( - id: entity.name, - label: entity.name, - radius: 18 + 20 * sizeFactor, - color: _kindColors[entity.kind] ?? _kindColors['concept']!, - kind: entity.kind, - isReflection: false, - offsetPhase: i * 0.7, - )); + _nodes.add( + _GraphNode( + id: entity.name, + label: entity.name, + radius: 18 + 20 * sizeFactor, + color: _kindColors[entity.kind] ?? _kindColors['concept']!, + kind: entity.kind, + isReflection: false, + offsetPhase: i * 0.7, + ), + ); } for (int i = 0; i < views.length && i < 6; i++) { - _nodes.add(_GraphNode( - id: 'kv_${views[i].title}', - label: views[i].title, - radius: 14, - color: const Color(0xFF8B7EC8), - kind: views[i].viewType, - isReflection: true, - offsetPhase: (entities.length + i) * 0.9, - )); + _nodes.add( + _GraphNode( + id: 'kv_${views[i].title}', + label: views[i].title, + radius: 14, + color: const Color(0xFF8B7EC8), + kind: views[i].viewType, + isReflection: true, + offsetPhase: (entities.length + i) * 0.9, + ), + ); } _layoutDone = false; @@ -2939,10 +2953,12 @@ class _EntityGraphPainter extends CustomPainter { void paint(Canvas canvas, Size size) { if (nodes.isEmpty) return; - final entityNodes = - nodes.where((n) => !n.isReflection).toList(growable: false); - final reflectionNodes = - nodes.where((n) => n.isReflection).toList(growable: false); + final entityNodes = nodes + .where((n) => !n.isReflection) + .toList(growable: false); + final reflectionNodes = nodes + .where((n) => n.isReflection) + .toList(growable: false); // Draw connections between entity nodes (subtle web) final linePaint = Paint() @@ -2974,8 +2990,8 @@ class _EntityGraphPainter extends CustomPainter { var closest = entityNodes.first; var minDist = double.infinity; for (final en in entityNodes) { - final d = (en.x - rn.x) * (en.x - rn.x) + - (en.y - rn.y) * (en.y - rn.y); + final d = + (en.x - rn.x) * (en.x - rn.x) + (en.y - rn.y) * (en.y - rn.y); if (d < minDist) { minDist = d; closest = en; @@ -3002,8 +3018,9 @@ class _EntityGraphPainter extends CustomPainter { // Glow if (isSelected || isHovered) { final glowPaint = Paint() - ..color = (isSelected ? accentColor : node.color) - .withValues(alpha: 0.22) + ..color = (isSelected ? accentColor : node.color).withValues( + alpha: 0.22, + ) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12); canvas.drawCircle(Offset(cx, cy), r + 6, glowPaint); } @@ -3023,8 +3040,8 @@ class _EntityGraphPainter extends CustomPainter { ..color = isSelected ? accentColor : (isHovered - ? node.color.withValues(alpha: 0.8) - : node.color.withValues(alpha: 0.35)) + ? node.color.withValues(alpha: 0.8) + : node.color.withValues(alpha: 0.35)) ..style = PaintingStyle.stroke ..strokeWidth = isSelected ? 2.5 : 1.5; canvas.drawCircle(Offset(cx, cy), r, borderPaint); @@ -3041,10 +3058,7 @@ class _EntityGraphPainter extends CustomPainter { maxLines: 1, ellipsis: '…', )..layout(maxWidth: r * 3); - tp.paint( - canvas, - Offset(cx - tp.width / 2, cy + r + 5), - ); + tp.paint(canvas, Offset(cx - tp.width / 2, cy + r + 5)); } } @@ -5365,6 +5379,7 @@ String _formatTaskWeekdays(Set weekdays) { if (sorted.isEmpty) return 'Monday'; return sorted.map((day) => _taskWeekdayLabels[day - 1]).join(', '); } + Future _pickTaskTriggerType( BuildContext context, String selectedType, @@ -6545,9 +6560,7 @@ class _TasksPanelState extends State { final selectedConnectionId = ValueNotifier( task?.triggerConfig['connectionId'] is int ? task!.triggerConfig['connectionId'] as int - : int.tryParse( - task?.triggerConfig['connectionId']?.toString() ?? '', - ), + : int.tryParse(task?.triggerConfig['connectionId']?.toString() ?? ''), ); final selectedDeliveryTarget = ValueNotifier( _taskDeliveryTargetFromTask(task), diff --git a/flutter_app/lib/main_settings.dart b/flutter_app/lib/main_settings.dart index a4188b48..438136a8 100644 --- a/flutter_app/lib/main_settings.dart +++ b/flutter_app/lib/main_settings.dart @@ -343,10 +343,11 @@ class _SettingsPanelState extends State { onPopInvokedWithResult: (didPop, result) async { if (didPop) return; final action = await _showLeaveDialog(context); - if (action == _LeaveAction.save && mounted) { + if (!context.mounted) return; + if (action == _LeaveAction.save) { await _doSave(); - if (mounted) Navigator.of(context).pop(); - } else if (action == _LeaveAction.discard && mounted) { + if (context.mounted) Navigator.of(context).pop(); + } else if (action == _LeaveAction.discard) { _hydrate(); setState(() => _hasUnsavedChanges = false); Navigator.of(context).pop(); diff --git a/flutter_app/lib/main_shared.dart b/flutter_app/lib/main_shared.dart index cdcbc480..e0ea1e2a 100644 --- a/flutter_app/lib/main_shared.dart +++ b/flutter_app/lib/main_shared.dart @@ -345,7 +345,6 @@ class _EntranceMotionState extends State<_EntranceMotion> { class _GlassSurface extends StatelessWidget { const _GlassSurface({ - super.key, required this.child, this.width, this.padding, @@ -2850,17 +2849,24 @@ String _ensureModelValue( if (allowAuto && value == 'auto') { return 'auto'; } - for (final model in models) { - if (model.id == value) { - return value; - } - } + final model = _modelForValue(value, models); + if (model != null) return model.id; if (allowAuto) { return 'auto'; } return models.isNotEmpty ? models.first.id : value; } +ModelMeta? _modelForValue(String value, List models) { + for (final model in models) { + if (model.id == value) return model; + } + for (final model in models) { + if (model.modelId == value) return model; + } + return null; +} + String _firstAvailableModelId(List models) { for (final model in models) { if (model.available) { @@ -2874,11 +2880,8 @@ String _modelLabelForValue(String value, List models) { if (value == 'auto' || value.trim().isEmpty) { return 'Auto'; } - for (final model in models) { - if (model.id == value) { - return model.label; - } - } + final model = _modelForValue(value, models); + if (model != null) return model.label; return value; } diff --git a/flutter_app/lib/src/desktop_companion_actions.dart b/flutter_app/lib/src/desktop_companion_actions.dart index 3d36eae7..3bac440f 100644 --- a/flutter_app/lib/src/desktop_companion_actions.dart +++ b/flutter_app/lib/src/desktop_companion_actions.dart @@ -17,6 +17,59 @@ import 'desktop_screen_capture.dart'; typedef _JpegArgs = ({Uint8List bytes, int quality}); +String resolveDesktopDisplaySelection( + Object? rawDisplays, + String requested, { + String? activeDisplayId, +}) { + final normalized = requested.trim(); + if (normalized.isEmpty) { + throw ArgumentError.value( + requested, + 'displayId', + 'Display ID is required.', + ); + } + final displays = rawDisplays is List + ? rawDisplays + .whereType() + .map( + (display) => + display.map((key, value) => MapEntry(key.toString(), value)), + ) + .where( + (display) => display['id']?.toString().trim().isNotEmpty == true, + ) + .toList(growable: false) + : const >[]; + if (displays.isEmpty) { + throw StateError('No desktop displays are currently available.'); + } + + if (normalized.toLowerCase() == 'primary') { + for (final display in displays) { + if (display['primary'] == true) { + return display['id'].toString().trim(); + } + } + final active = activeDisplayId?.trim() ?? ''; + if (active.isNotEmpty && + displays.any((display) => display['id']?.toString().trim() == active)) { + return active; + } + } + + for (final display in displays) { + final id = display['id']?.toString().trim() ?? ''; + if (id == normalized) return id; + } + throw ArgumentError.value( + requested, + 'displayId', + 'The requested desktop display is not available.', + ); +} + Uint8List _compressJpegInIsolate(_JpegArgs args) { final decoded = img.decodeImage(args.bytes); if (decoded == null) return args.bytes; @@ -47,6 +100,8 @@ class DesktopCompanionActions { final DesktopScreenCapture _screenCapture; final DesktopNativeBridge _nativeBridge = DesktopNativeBridge(); + final Map _shellProcesses = {}; + final Set _cancelledShellCommandIds = {}; bool get isCaptureSupported => _screenCapture.isSupported; @@ -64,6 +119,7 @@ class DesktopCompanionActions { activeDisplayId: activeDisplayId, platformStatus: platformStatus, ); + final reportedDisplays = _coerceDisplays(platformStatus['displays']); final packageInfo = await PackageInfo.fromPlatform(); return { 'deviceId': deviceId, @@ -77,7 +133,7 @@ class DesktopCompanionActions { 'paused': paused, 'permissions': _permissions(capabilities, platformStatus: platformStatus), 'capabilities': capabilities, - 'displays': snapshot?.displays ?? const >[], + 'displays': snapshot?.displays ?? reportedDisplays, 'activeDisplayId': snapshot?.activeDisplayId ?? platformStatus['activeDisplayId']?.toString() ?? @@ -144,10 +200,10 @@ class DesktopCompanionActions { contentType: capture.mimeType, width: width, height: height, - activeDisplayId: activeDisplayId ?? 'primary', + activeDisplayId: 'primary', displays: >[ { - 'id': activeDisplayId ?? 'primary', + 'id': 'primary', 'label': 'Primary Display', 'width': width, 'height': height, @@ -169,6 +225,7 @@ class DesktopCompanionActions { activeDisplayId: activeDisplayId, platformStatus: platformStatus, ); + final reportedDisplays = _coerceDisplays(platformStatus['displays']); return { 'paused': paused, 'label': label, @@ -177,7 +234,7 @@ class DesktopCompanionActions { platformStatus['activeDisplayId']?.toString() ?? activeDisplayId ?? 'primary', - 'displays': snapshot?.displays ?? const >[], + 'displays': snapshot?.displays ?? reportedDisplays, 'permissions': _permissions(capabilities, platformStatus: platformStatus), 'capabilities': capabilities, if (platformStatus['frontmostApp'] != null) @@ -461,11 +518,17 @@ class DesktopCompanionActions { } Future> executeShellCommand({ + required String commandId, required String command, String? cwd, int? timeoutMs, String? stdinInput, + bool requestedPty = false, + List inputs = const [], }) async { + if (command.trim().isEmpty) { + throw ArgumentError.value(command, 'command', 'Command is required.'); + } final shell = Platform.isWindows ? 'cmd.exe' : (Platform.environment['SHELL'] ?? '/bin/sh'); @@ -483,9 +546,18 @@ class DesktopCompanionActions { workingDirectory: workingDir, runInShell: false, ); + if (commandId.isNotEmpty) { + _shellProcesses[commandId] = process; + } - if (stdinInput != null && stdinInput.isNotEmpty) { - process.stdin.write(stdinInput); + if (_cancelledShellCommandIds.contains(commandId)) { + await _terminateShellProcess(process); + } else if ((stdinInput != null && stdinInput.isNotEmpty) || + inputs.isNotEmpty) { + if (stdinInput != null) process.stdin.write(stdinInput); + for (final input in inputs) { + process.stdin.write(input); + } await process.stdin.close(); } else { unawaited(process.stdin.close()); @@ -494,13 +566,41 @@ class DesktopCompanionActions { const maxChars = 50000; final stdoutBuf = StringBuffer(); final stderrBuf = StringBuffer(); - - final stdoutSub = process.stdout.transform(utf8.decoder).listen((data) { - stdoutBuf.write(data); - }); - final stderrSub = process.stderr.transform(utf8.decoder).listen((data) { - stderrBuf.write(data); - }); + var stdoutChars = 0; + var stderrChars = 0; + final stdoutDone = Completer(); + final stderrDone = Completer(); + + final stdoutSub = process.stdout + .transform(utf8.decoder) + .listen( + (data) { + stdoutChars += data.length; + final remaining = maxChars - stdoutBuf.length; + if (remaining > 0) { + stdoutBuf.write( + data.substring(0, data.length.clamp(0, remaining)), + ); + } + }, + onError: stdoutDone.completeError, + onDone: stdoutDone.complete, + ); + final stderrSub = process.stderr + .transform(utf8.decoder) + .listen( + (data) { + stderrChars += data.length; + final remaining = maxChars - stderrBuf.length; + if (remaining > 0) { + stderrBuf.write( + data.substring(0, data.length.clamp(0, remaining)), + ); + } + }, + onError: stderrDone.completeError, + onDone: stderrDone.complete, + ); final effectiveTimeout = Duration( milliseconds: (timeoutMs != null && timeoutMs > 0) @@ -509,38 +609,81 @@ class DesktopCompanionActions { ); bool timedOut = false; + bool externallyCancelled = false; int? exitCode; try { exitCode = await process.exitCode.timeout(effectiveTimeout); } on TimeoutException { timedOut = true; - process.kill(ProcessSignal.sigterm); - exitCode = null; + exitCode = await _terminateShellProcess(process); + } finally { + externallyCancelled = _cancelledShellCommandIds.contains(commandId); + if (commandId.isNotEmpty) { + _shellProcesses.remove(commandId); + _cancelledShellCommandIds.remove(commandId); + } } - await stdoutSub.cancel(); - await stderrSub.cancel(); + try { + await Future.wait(>[ + stdoutDone.future, + stderrDone.future, + ]).timeout(const Duration(seconds: 2)); + } on TimeoutException { + await stdoutSub.cancel(); + await stderrSub.cancel(); + } - String trimOutput(StringBuffer buf) { + String trimOutput(StringBuffer buf, int totalChars) { final s = buf.toString().trim(); - return s.length > maxChars - ? '${s.substring(0, maxChars)}\n...[truncated, ${s.length} total chars]' + return totalChars > maxChars + ? '$s\n...[truncated, $totalChars total chars]' : s; } return { 'exitCode': exitCode, - 'stdout': trimOutput(stdoutBuf), - 'stderr': trimOutput(stderrBuf), + 'stdout': trimOutput(stdoutBuf, stdoutChars), + 'stderr': trimOutput(stderrBuf, stderrChars), 'timedOut': timedOut, - 'killed': timedOut, + 'killed': timedOut || externallyCancelled, + 'cancelled': externallyCancelled, 'durationMs': DateTime.now().difference(startedAt).inMilliseconds, 'command': command, 'cwd': workingDir, 'backend': 'desktop-companion', + 'ptyRequested': requestedPty, + 'ptyAllocated': false, }; } + Future> cancelShellCommand(String commandId) async { + if (commandId.isEmpty) { + return {'success': false, 'cancelled': false}; + } + _cancelledShellCommandIds.add(commandId); + final process = _shellProcesses[commandId]; + if (process == null) { + return {'success': true, 'cancelled': true}; + } + await _terminateShellProcess(process); + return {'success': true, 'cancelled': true}; + } + + Future _terminateShellProcess(Process process) async { + process.kill(ProcessSignal.sigterm); + try { + return await process.exitCode.timeout(const Duration(seconds: 2)); + } on TimeoutException { + process.kill(ProcessSignal.sigkill); + try { + return await process.exitCode.timeout(const Duration(seconds: 2)); + } on TimeoutException { + return null; + } + } + } + Future> _capabilities({ Map? platformStatus, }) async { @@ -687,12 +830,13 @@ class DesktopCompanionActions { 'IdleHint', ]); if (result.exitCode == 0) { - final lines = result.stdout - ?.toString() - .split(RegExp(r'\r?\n')) - .map((line) => line.trim()) - .where((line) => line.isNotEmpty) - .toList(growable: false) ?? + final lines = + result.stdout + ?.toString() + .split(RegExp(r'\r?\n')) + .map((line) => line.trim()) + .where((line) => line.isNotEmpty) + .toList(growable: false) ?? const []; for (final line in lines) { if (line.startsWith('LockedHint=')) { @@ -713,8 +857,7 @@ class DesktopCompanionActions { if (idleMs != null) { final idleSeconds = idleMs / 1000; state['idleSeconds'] = idleSeconds; - state['userIdle'] = - (state['userIdle'] == true) || idleSeconds >= 300; + state['userIdle'] = (state['userIdle'] == true) || idleSeconds >= 300; } } } catch (_) {} @@ -814,6 +957,17 @@ class DesktopCompanionActions { ]; } + List> _coerceDisplays(Object? raw) { + if (raw is! List) return const >[]; + return raw + .whereType() + .map( + (item) => item.map((key, value) => MapEntry(key.toString(), value)), + ) + .where((item) => item['id']?.toString().trim().isNotEmpty == true) + .toList(growable: false); + } + Future _run(_ShellCommand command) async { final result = await Process.run(command.command, command.args); if (result.exitCode != 0) { diff --git a/flutter_app/lib/src/desktop_companion_io.dart b/flutter_app/lib/src/desktop_companion_io.dart index 44c2f453..1ed58bb8 100644 --- a/flutter_app/lib/src/desktop_companion_io.dart +++ b/flutter_app/lib/src/desktop_companion_io.dart @@ -26,6 +26,7 @@ class DesktopCompanionManager extends ChangeNotifier { WebSocket? _socket; Timer? _reconnectTimer; Timer? _connectionWatchdogTimer; + Timer? _helloTimer; Timer? _streamTimer; bool _streamCaptureInFlight = false; // Set true while a click / drag / scroll / typeText / pressKey command is @@ -38,6 +39,13 @@ class DesktopCompanionManager extends ChangeNotifier { // Tracks the current stream quality so the forced post-input capture can use // the same setting without re-parsing the original startStream payload. int _currentStreamQuality = 80; + Future _inputCommandQueue = Future.value(); + final Set _pendingCommandIds = {}; + final Set _pendingShellCommandIds = {}; + final Set _cancelledCommandIds = {}; + int _connectionGeneration = 0; + int _reconnectAttempt = 0; + bool _disposed = false; String _backendUrl = ''; String _sessionCookie = ''; @@ -63,6 +71,10 @@ class DesktopCompanionManager extends ChangeNotifier { String get activationId => _activationId; Map get status => _status; + void _notify() { + if (!_disposed) notifyListeners(); + } + Future bootstrap(SharedPreferences prefs) async { _enabled = prefs.getBool(desktopCompanionEnabledPrefsKey) ?? false; // Always start unpaused — paused state must not carry over across restarts. @@ -88,13 +100,20 @@ class DesktopCompanionManager extends ChangeNotifier { required String sessionCookie, required bool authenticated, }) async { - _backendUrl = backendUrl.trim(); - _sessionCookie = sessionCookie.trim(); + final nextBackendUrl = backendUrl.trim(); + final nextSessionCookie = sessionCookie.trim(); + final sessionChanged = + _backendUrl != nextBackendUrl || _sessionCookie != nextSessionCookie; + _backendUrl = nextBackendUrl; + _sessionCookie = nextSessionCookie; _authenticated = authenticated; if (!_authenticated || !_enabled || _sessionCookie.isEmpty) { await disconnect(); return; } + if (sessionChanged && (_connected || _connecting || _socket != null)) { + await disconnect(); + } _ensureConnectionWatchdog(); await _ensureConnected(); } @@ -110,7 +129,7 @@ class DesktopCompanionManager extends ChangeNotifier { ); } await prefs.setBool(desktopCompanionEnabledPrefsKey, value); - notifyListeners(); + _notify(); if (!value) { await disconnect(); return; @@ -122,7 +141,7 @@ class DesktopCompanionManager extends ChangeNotifier { final normalized = value.trim().isEmpty ? _defaultLabel() : value.trim(); _label = normalized; await prefs.setString(desktopCompanionLabelPrefsKey, normalized); - notifyListeners(); + _notify(); if (_connected) { _status = {..._status, 'label': normalized}; await _sendEvent('statusChanged', {'label': normalized}); @@ -131,28 +150,30 @@ class DesktopCompanionManager extends ChangeNotifier { Future setPaused(bool value, SharedPreferences prefs) async { _paused = value; - notifyListeners(); + _notify(); if (_connected) { await _sendEvent('statusChanged', {'paused': value}); } } Future disconnect() async { + _connectionGeneration++; _reconnectTimer?.cancel(); _reconnectTimer = null; _connectionWatchdogTimer?.cancel(); _connectionWatchdogTimer = null; + _helloTimer?.cancel(); + _helloTimer = null; _stopStreaming(); _connecting = false; _connected = false; + _cancelPendingCommands(); final socket = _socket; _socket = null; if (socket != null) { - try { - await socket.close(); - } catch (_) {} + await _closeSocket(socket); } - notifyListeners(); + _notify(); } Future rotateIdentity(SharedPreferences prefs) async { @@ -181,7 +202,7 @@ class DesktopCompanionManager extends ChangeNotifier { 'companionEnabled': _enabled, 'paused': _paused, }; - notifyListeners(); + _notify(); if (_connected) { await _sendEvent('statusChanged', { 'permissions': _status['permissions'], @@ -217,65 +238,122 @@ class DesktopCompanionManager extends ChangeNotifier { } Future _ensureConnected() async { - if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return; + if (_disposed || !_enabled || !_authenticated || _sessionCookie.isEmpty) { + return; + } if (_connecting || _connected) return; + final generation = ++_connectionGeneration; _connecting = true; _errorMessage = null; - notifyListeners(); + _notify(); + WebSocket? connectedSocket; try { final uri = _desktopWsUri(_backendUrl); - final socket = await WebSocket.connect( - uri.toString(), - headers: {'Cookie': _sessionCookie}, - ); + final socket = await _openWebSocket(uri); + connectedSocket = socket; + if (_disposed || generation != _connectionGeneration) { + await _closeSocket(socket); + return; + } socket.pingInterval = const Duration(seconds: 25); _socket = socket; socket.listen( - _handleMessage, - onDone: _handleSocketClosed, + (dynamic raw) => _handleMessage(socket, generation, raw), + onDone: () => _handleSocketClosed(socket, generation), onError: (Object error, StackTrace stackTrace) { - _errorMessage = '$error'; - _handleSocketClosed(); + if (_socket == socket && generation == _connectionGeneration) { + _errorMessage = '$error'; + } + _handleSocketClosed(socket, generation); }, cancelOnError: true, ); - final hello = await _actions.buildHello( - deviceId: _deviceId, - activationId: _activationId, - label: _label, - companionEnabled: _enabled, - paused: _paused, - activeDisplayId: _activeDisplayId, - ); + final hello = await _actions + .buildHello( + deviceId: _deviceId, + activationId: _activationId, + label: _label, + companionEnabled: _enabled, + paused: _paused, + activeDisplayId: _activeDisplayId, + ) + .timeout(const Duration(seconds: 10)); + if (_socket != socket || generation != _connectionGeneration) return; socket.add( jsonEncode({'type': 'hello', 'device': hello}), ); + _helloTimer?.cancel(); + _helloTimer = Timer(const Duration(seconds: 10), () { + if (_socket != socket || + generation != _connectionGeneration || + _connected) { + return; + } + _errorMessage = 'Desktop companion handshake timed out.'; + _handleSocketClosed(socket, generation); + }); } catch (error) { + if (connectedSocket != null && connectedSocket == _socket) { + _socket = null; + unawaited(_closeSocket(connectedSocket)); + } + if (_disposed || generation != _connectionGeneration) return; _connecting = false; _connected = false; _errorMessage = '$error'; - notifyListeners(); + _notify(); _scheduleReconnect(); } } - void _handleMessage(dynamic raw) { + Future _openWebSocket(Uri uri) async { + final pending = WebSocket.connect( + uri.toString(), + headers: {'Cookie': _sessionCookie}, + ); + try { + return await pending.timeout(const Duration(seconds: 15)); + } on TimeoutException { + unawaited(() async { + try { + final lateSocket = await pending; + await _closeSocket(lateSocket); + } catch (_) {} + }()); + throw TimeoutException( + 'Desktop companion connection timed out.', + const Duration(seconds: 15), + ); + } + } + + Future _closeSocket(WebSocket socket) async { + try { + await socket.close().timeout(const Duration(seconds: 2)); + } catch (_) {} + } + + void _handleMessage(WebSocket source, int generation, dynamic raw) { + if (_socket != source || generation != _connectionGeneration) return; try { final message = jsonDecode(raw as String); if (message is! Map) return; final type = message['type']?.toString() ?? ''; if (type == 'hello') { + _helloTimer?.cancel(); + _helloTimer = null; _connecting = false; final ok = message['ok'] == true; if (!ok) { _connected = false; _errorMessage = message['error']?.toString() ?? 'Desktop companion rejected.'; - notifyListeners(); - _handleSocketClosed(); + _notify(); + _handleSocketClosed(source, generation); return; } _connected = true; + _reconnectAttempt = 0; _errorMessage = null; final device = message['device']; _status = device is Map @@ -283,18 +361,37 @@ class DesktopCompanionManager extends ChangeNotifier { : const {}; _activeDisplayId = _status['activeDisplayId']?.toString() ?? _activeDisplayId; - notifyListeners(); + _notify(); return; } if (type != 'command') return; - unawaited(_handleCommand(message.cast())); + final commandMessage = message.cast(); + final command = commandMessage['command']?.toString() ?? ''; + final commandId = commandMessage['id']?.toString() ?? ''; + if (command != 'cancelCommand' && commandId.isNotEmpty) { + _pendingCommandIds.add(commandId); + if (command == 'executeCommand') { + _pendingShellCommandIds.add(commandId); + } + } + if (_inputCommands.contains(command)) { + final previous = _inputCommandQueue; + _inputCommandQueue = () async { + try { + await previous; + } catch (_) {} + await _handleCommand(commandMessage, source, generation); + }(); + } else { + unawaited(_handleCommand(commandMessage, source, generation)); + } } on FormatException catch (error) { _errorMessage = 'Ignored malformed desktop companion message: $error'; - notifyListeners(); + _notify(); return; } catch (error) { _errorMessage = 'Desktop companion message handling failed: $error'; - notifyListeners(); + _notify(); return; } } @@ -312,7 +409,11 @@ class DesktopCompanionManager extends ChangeNotifier { 'pressKey', }; - Future _handleCommand(Map message) async { + Future _handleCommand( + Map message, + WebSocket source, + int generation, + ) async { final id = message['id']?.toString() ?? ''; final command = message['command']?.toString() ?? ''; final payload = message['payload'] is Map @@ -321,23 +422,52 @@ class DesktopCompanionManager extends ChangeNotifier { ) : const {}; + if (_socket != source || generation != _connectionGeneration) { + _pendingCommandIds.remove(id); + _pendingShellCommandIds.remove(id); + _cancelledCommandIds.remove(id); + return; + } + final isInput = _inputCommands.contains(command); if (isInput) _inputCommandInFlight = true; try { - final response = await _dispatchCommand(command, payload); - _socket?.add( - jsonEncode({ + if (command != 'cancelCommand' && _cancelledCommandIds.contains(id)) { + _sendCommandResult(source, generation, { 'type': 'result', 'id': id, - 'ok': true, - 'payload': response, - }), - ); + 'ok': false, + 'code': 'COMMAND_CANCELLED', + 'error': 'Desktop companion command was cancelled.', + }); + return; + } + final response = await _dispatchCommand(command, payload, commandId: id); + if (command != 'cancelCommand' && _cancelledCommandIds.contains(id)) { + _sendCommandResult(source, generation, { + 'type': 'result', + 'id': id, + 'ok': false, + 'code': 'COMMAND_CANCELLED', + 'error': 'Desktop companion command was cancelled.', + }); + return; + } + _sendCommandResult(source, generation, { + 'type': 'result', + 'id': id, + 'ok': true, + 'payload': response, + }); // Immediately capture a fresh frame after an input action so the user // sees the result of their interaction without waiting for the next // timer tick. - if (isInput && _streamTimer != null && _connected) { + if (isInput && + _streamTimer != null && + _connected && + _socket == source && + generation == _connectionGeneration) { unawaited( _captureAndSendBinaryFrame( _currentStreamQuality, @@ -347,24 +477,43 @@ class DesktopCompanionManager extends ChangeNotifier { ); } } catch (error) { - _socket?.add( - jsonEncode({ - 'type': 'result', - 'id': id, - 'ok': false, - 'error': '$error', - }), - ); + _sendCommandResult(source, generation, { + 'type': 'result', + 'id': id, + 'ok': false, + 'error': '$error', + }); } finally { if (isInput) _inputCommandInFlight = false; + _pendingCommandIds.remove(id); + _pendingShellCommandIds.remove(id); + _cancelledCommandIds.remove(id); + } + } + + void _sendCommandResult( + WebSocket source, + int generation, + Map message, + ) { + if (_socket != source || generation != _connectionGeneration) return; + try { + source.add(jsonEncode(message)); + } catch (error) { + _errorMessage = 'Desktop companion response failed: $error'; + _handleSocketClosed(source, generation); } } Future> _dispatchCommand( String command, - Map payload, - ) async { - if (_paused && command != 'getStatus' && command != 'pauseControl') { + Map payload, { + required String commandId, + }) async { + if (_paused && + command != 'getStatus' && + command != 'pauseControl' && + command != 'cancelCommand') { throw Exception('Desktop companion is paused locally.'); } switch (command) { @@ -387,23 +536,23 @@ class DesktopCompanionManager extends ChangeNotifier { ); case 'click': return _actions.click( - x: (payload['x'] as num?)?.round() ?? 0, - y: (payload['y'] as num?)?.round() ?? 0, + x: _requiredCoordinate(payload, 'x'), + y: _requiredCoordinate(payload, 'y'), button: payload['button']?.toString() ?? 'left', displayId: _activeDisplayId, ); case 'mouseMove': return _actions.mouseMove( - x: (payload['x'] as num?)?.round() ?? 0, - y: (payload['y'] as num?)?.round() ?? 0, + x: _requiredCoordinate(payload, 'x'), + y: _requiredCoordinate(payload, 'y'), displayId: _activeDisplayId, ); case 'drag': return _actions.drag( - x1: (payload['x1'] as num?)?.round() ?? 0, - y1: (payload['y1'] as num?)?.round() ?? 0, - x2: (payload['x2'] as num?)?.round() ?? 0, - y2: (payload['y2'] as num?)?.round() ?? 0, + x1: _requiredCoordinate(payload, 'x1'), + y1: _requiredCoordinate(payload, 'y1'), + x2: _requiredCoordinate(payload, 'x2'), + y2: _requiredCoordinate(payload, 'y2'), durationMs: (payload['durationMs'] as num?)?.round() ?? 280, displayId: _activeDisplayId, ); @@ -433,28 +582,54 @@ class DesktopCompanionManager extends ChangeNotifier { 'activeDisplayId': status['activeDisplayId'] ?? 'primary', }; case 'selectDisplay': - final displayId = payload['displayId']?.toString() ?? 'primary'; - _activeDisplayId = displayId; + final displayId = await _resolveDisplaySelection( + payload['displayId']?.toString() ?? '', + ); final prefs = await SharedPreferences.getInstance(); - await prefs.setString(desktopCompanionActiveDisplayPrefsKey, displayId); + final persisted = await prefs.setString( + desktopCompanionActiveDisplayPrefsKey, + displayId, + ); + if (!persisted) { + throw StateError('Unable to persist the selected desktop display.'); + } + _activeDisplayId = displayId; _status = {..._status, 'activeDisplayId': displayId}; - // TODO: Apply platform-specific active display switching when available. - notifyListeners(); + _notify(); return {'success': true, 'activeDisplayId': displayId}; case 'getTree': return _actions.getTree(); case 'pauseControl': final paused = payload['paused'] != false; _paused = paused; - notifyListeners(); + _notify(); return {'success': true, 'paused': _paused}; case 'executeCommand': return _actions.executeShellCommand( + commandId: commandId, command: payload['command']?.toString() ?? '', cwd: payload['cwd']?.toString(), timeoutMs: (payload['timeout'] as num?)?.toInt(), stdinInput: payload['stdin_input']?.toString(), + requestedPty: payload['pty'] == true, + inputs: payload['inputs'] is List + ? (payload['inputs'] as List) + .map((value) => value.toString()) + .toList(growable: false) + : const [], ); + case 'cancelCommand': + final targetId = payload['commandId']?.toString() ?? ''; + if (targetId.isNotEmpty && _pendingCommandIds.contains(targetId)) { + _cancelledCommandIds.add(targetId); + } + if (_pendingShellCommandIds.contains(targetId)) { + return _actions.cancelShellCommand(targetId); + } + return { + 'success': targetId.isNotEmpty, + 'cancelled': _cancelledCommandIds.contains(targetId), + }; case 'ping': return {'pong': true}; default: @@ -462,49 +637,86 @@ class DesktopCompanionManager extends ChangeNotifier { } } - void _handleSocketClosed() { + int _requiredCoordinate(Map payload, String key) { + final value = payload[key]; + if (value is! num || !value.isFinite) { + throw FormatException('$key must be a finite number.'); + } + return value.round(); + } + + void _handleSocketClosed(WebSocket source, int generation) { + if (_socket != source || generation != _connectionGeneration) return; + _connectionGeneration++; + _helloTimer?.cancel(); + _helloTimer = null; _stopStreaming(); _socket = null; _connecting = false; _connected = false; - notifyListeners(); + _cancelPendingCommands(); + unawaited(_closeSocket(source)); + if (_disposed) return; + _notify(); _scheduleReconnect(); } + void _cancelPendingCommands() { + for (final commandId in _pendingCommandIds.toList(growable: false)) { + _cancelledCommandIds.add(commandId); + if (_pendingShellCommandIds.contains(commandId)) { + unawaited(_actions.cancelShellCommand(commandId)); + } + } + } + @override void dispose() { + _disposed = true; + _connectionGeneration++; _reconnectTimer?.cancel(); _reconnectTimer = null; _connectionWatchdogTimer?.cancel(); _connectionWatchdogTimer = null; + _helloTimer?.cancel(); + _helloTimer = null; _stopStreaming(); _connecting = false; _connected = false; _enabled = false; + _cancelPendingCommands(); final socket = _socket; _socket = null; if (socket != null) { - try { - socket.close(); - } catch (_) {} + unawaited(_closeSocket(socket)); } super.dispose(); } void _scheduleReconnect() { - if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return; + if (_disposed || !_enabled || !_authenticated || _sessionCookie.isEmpty) { + return; + } _ensureConnectionWatchdog(); _reconnectTimer?.cancel(); - _reconnectTimer = Timer(const Duration(seconds: 5), () { - unawaited(_ensureConnected()); - }); + final exponentialMs = min(60000, 1000 * (1 << min(_reconnectAttempt, 6))); + _reconnectAttempt++; + final jitterMs = Random().nextInt(max(1, exponentialMs ~/ 4)); + _reconnectTimer = Timer( + Duration(milliseconds: exponentialMs + jitterMs), + () { + unawaited(_ensureConnected()); + }, + ); } void _ensureConnectionWatchdog() { - if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return; + if (_disposed || !_enabled || !_authenticated || _sessionCookie.isEmpty) { + return; + } if (_connectionWatchdogTimer != null) return; _connectionWatchdogTimer = Timer.periodic(const Duration(seconds: 30), (_) { - if (!_enabled || !_authenticated || _sessionCookie.isEmpty) { + if (_disposed || !_enabled || !_authenticated || _sessionCookie.isEmpty) { _connectionWatchdogTimer?.cancel(); _connectionWatchdogTimer = null; return; @@ -530,14 +742,16 @@ class DesktopCompanionManager extends ChangeNotifier { Future> _startStreaming( Map payload, ) async { - _streamTimer?.cancel(); - final generation = ++_streamGeneration; final fps = ((payload['fps'] as num?)?.round() ?? 15).clamp(1, 20); final quality = ((payload['quality'] as num?)?.round() ?? 80).clamp(30, 95); final displayId = payload['displayId']?.toString().trim(); + var selectedDisplayId = _activeDisplayId; if (displayId != null && displayId.isNotEmpty) { - _activeDisplayId = displayId; + selectedDisplayId = await _resolveDisplaySelection(displayId); } + _streamTimer?.cancel(); + final generation = ++_streamGeneration; + _activeDisplayId = selectedDisplayId; final interval = Duration(milliseconds: max(1, (1000 / fps).floor())); _frameSeq = 0; _currentStreamQuality = quality; @@ -553,6 +767,19 @@ class DesktopCompanionManager extends ChangeNotifier { }; } + Future _resolveDisplaySelection(String requested) async { + final status = await _actions.getStatus( + label: _label, + paused: _paused, + activeDisplayId: _activeDisplayId, + ); + return resolveDesktopDisplaySelection( + status['displays'], + requested, + activeDisplayId: status['activeDisplayId']?.toString(), + ); + } + Map _stopStreaming() { _streamTimer?.cancel(); _streamTimer = null; @@ -601,7 +828,7 @@ class DesktopCompanionManager extends ChangeNotifier { socket.add(frame); } catch (error) { _errorMessage = 'Desktop stream capture failed: $error'; - notifyListeners(); + _notify(); } finally { _streamCaptureInFlight = false; } @@ -671,7 +898,13 @@ class DesktopCompanionManager extends ChangeNotifier { Uri _desktopWsUri(String backendUrl) { final base = Uri.parse(backendUrl); final scheme = base.scheme == 'https' ? 'wss' : 'ws'; - return base.replace(scheme: scheme, path: '/api/desktop/ws', query: ''); + final basePath = base.path.replaceFirst(RegExp(r'/+$'), ''); + return base.replace( + scheme: scheme, + path: '$basePath/api/desktop/ws', + query: '', + fragment: '', + ); } String _defaultLabel() { diff --git a/flutter_app/windows/runner/flutter_window.cpp b/flutter_app/windows/runner/flutter_window.cpp index 5631a29a..75de4505 100644 --- a/flutter_app/windows/runner/flutter_window.cpp +++ b/flutter_app/windows/runner/flutter_window.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -162,6 +163,24 @@ DisplayInfo ResolveDisplay(const std::string& requested_id) { : displays.front(); } +POINT CapturedPixelToDesktopPoint(const DisplayInfo& display, int x, int y) { + const int width = std::max( + 1, static_cast(display.rect.right - display.rect.left)); + const int height = std::max( + 1, static_cast(display.rect.bottom - display.rect.top)); + return POINT{ + display.rect.left + std::clamp(x, 0, width - 1), + display.rect.top + std::clamp(y, 0, height - 1), + }; +} + +POINT DisplayCenterPoint(const DisplayInfo& display) { + return POINT{ + display.rect.left + ((display.rect.right - display.rect.left) / 2), + display.rect.top + ((display.rect.bottom - display.rect.top) / 2), + }; +} + CLSID PngEncoderClsid() { UINT count = 0; UINT size = 0; @@ -219,6 +238,10 @@ EncodableList DisplaysToEncodable() { EncodableMap item; item[EncodableValue("id")] = EncodableValue(WideToUtf8(display.id)); item[EncodableValue("label")] = EncodableValue(WideToUtf8(display.label)); + item[EncodableValue("x")] = + EncodableValue(static_cast(display.rect.left)); + item[EncodableValue("y")] = + EncodableValue(static_cast(display.rect.top)); item[EncodableValue("width")] = EncodableValue(width); item[EncodableValue("height")] = EncodableValue(height); item[EncodableValue("scaleFactor")] = EncodableValue(1.0); @@ -305,18 +328,19 @@ bool IsWorkstationLocked() { return _wcsicmp(name, L"Default") != 0; } -void SendMouseButton(DWORD flag) { +bool SendMouseButton(DWORD flag) { INPUT input{}; input.type = INPUT_MOUSE; input.mi.dwFlags = flag; - SendInput(1, &input, sizeof(INPUT)); + return SendInput(1, &input, sizeof(INPUT)) == 1; } WORD VirtualKeyForString(const std::string& key) { const std::string lowered = [&]() { std::string value = key; for (auto& ch : value) { - ch = static_cast(tolower(ch)); + ch = static_cast( + std::tolower(static_cast(ch))); } return value; }(); @@ -332,17 +356,21 @@ WORD VirtualKeyForString(const std::string& key) { return 0; } -void SendVirtualKey(WORD key_code) { +bool SendVirtualKey(WORD key_code) { INPUT inputs[2]{}; inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = key_code; inputs[1].type = INPUT_KEYBOARD; inputs[1].ki.wVk = key_code; inputs[1].ki.dwFlags = KEYEVENTF_KEYUP; - SendInput(2, inputs, sizeof(INPUT)); + const UINT sent = SendInput(2, inputs, sizeof(INPUT)); + if (sent == 1) { + SendInput(1, &inputs[1], sizeof(INPUT)); + } + return sent == 2; } -void SendUnicodeText(const std::wstring& text) { +bool SendUnicodeText(const std::wstring& text) { for (const wchar_t ch : text) { INPUT inputs[2]{}; inputs[0].type = INPUT_KEYBOARD; @@ -351,8 +379,13 @@ void SendUnicodeText(const std::wstring& text) { inputs[1].type = INPUT_KEYBOARD; inputs[1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; inputs[1].ki.wScan = ch; - SendInput(2, inputs, sizeof(INPUT)); + const UINT sent = SendInput(2, inputs, sizeof(INPUT)); + if (sent == 1) { + SendInput(1, &inputs[1], sizeof(INPUT)); + } + if (sent != 2) return false; } + return true; } } // namespace @@ -462,19 +495,59 @@ bool FlutterWindow::OnCreate() { } int x = 0; int y = 0; - GetInt(*arguments, "x", &x); - GetInt(*arguments, "y", &y); + if (!GetInt(*arguments, "x", &x) || + !GetInt(*arguments, "y", &y)) { + result->Error("invalid_arguments", "Missing click coordinates."); + return; + } const std::string button = GetString(*arguments, "button", "left"); - SetCursorPos(x, y); + const DisplayInfo display = + ResolveDisplay(GetString(*arguments, "displayId")); + const POINT point = CapturedPixelToDesktopPoint(display, x, y); + if (!SetCursorPos(point.x, point.y)) { + result->Error("input_failed", "Unable to move the desktop cursor."); + return; + } + bool sent = false; if (button == "right") { - SendMouseButton(MOUSEEVENTF_RIGHTDOWN); - SendMouseButton(MOUSEEVENTF_RIGHTUP); + const bool down = SendMouseButton(MOUSEEVENTF_RIGHTDOWN); + const bool up = SendMouseButton(MOUSEEVENTF_RIGHTUP); + sent = down && up; } else if (button == "middle") { - SendMouseButton(MOUSEEVENTF_MIDDLEDOWN); - SendMouseButton(MOUSEEVENTF_MIDDLEUP); + const bool down = SendMouseButton(MOUSEEVENTF_MIDDLEDOWN); + const bool up = SendMouseButton(MOUSEEVENTF_MIDDLEUP); + sent = down && up; } else { - SendMouseButton(MOUSEEVENTF_LEFTDOWN); - SendMouseButton(MOUSEEVENTF_LEFTUP); + const bool down = SendMouseButton(MOUSEEVENTF_LEFTDOWN); + const bool up = SendMouseButton(MOUSEEVENTF_LEFTUP); + sent = down && up; + } + if (!sent) { + result->Error("input_failed", "Unable to send the desktop click."); + return; + } + result->Success(EncodableValue()); + return; + } + + if (call.method_name() == "mouseMove") { + if (arguments == nullptr) { + result->Error("invalid_arguments", "Missing mouseMove payload."); + return; + } + int x = 0; + int y = 0; + if (!GetInt(*arguments, "x", &x) || + !GetInt(*arguments, "y", &y)) { + result->Error("invalid_arguments", "Missing mouseMove coordinates."); + return; + } + const DisplayInfo display = + ResolveDisplay(GetString(*arguments, "displayId")); + const POINT point = CapturedPixelToDesktopPoint(display, x, y); + if (!SetCursorPos(point.x, point.y)) { + result->Error("input_failed", "Unable to move the desktop cursor."); + return; } result->Success(EncodableValue()); return; @@ -486,22 +559,40 @@ bool FlutterWindow::OnCreate() { return; } int x1 = 0, y1 = 0, x2 = 0, y2 = 0, duration_ms = 280; - GetInt(*arguments, "x1", &x1); - GetInt(*arguments, "y1", &y1); - GetInt(*arguments, "x2", &x2); - GetInt(*arguments, "y2", &y2); + if (!GetInt(*arguments, "x1", &x1) || + !GetInt(*arguments, "y1", &y1) || + !GetInt(*arguments, "x2", &x2) || + !GetInt(*arguments, "y2", &y2)) { + result->Error("invalid_arguments", "Missing drag coordinates."); + return; + } GetInt(*arguments, "durationMs", &duration_ms); + duration_ms = std::clamp(duration_ms, 40, 2000); + const DisplayInfo display = + ResolveDisplay(GetString(*arguments, "displayId")); + const POINT start = CapturedPixelToDesktopPoint(display, x1, y1); + const POINT end = CapturedPixelToDesktopPoint(display, x2, y2); const int steps = std::max(4, std::min(90, duration_ms / 16)); - SetCursorPos(x1, y1); - SendMouseButton(MOUSEEVENTF_LEFTDOWN); + if (!SetCursorPos(start.x, start.y) || + !SendMouseButton(MOUSEEVENTF_LEFTDOWN)) { + result->Error("input_failed", "Unable to begin the desktop drag."); + return; + } + bool moved = true; for (int step = 1; step <= steps; ++step) { const double t = static_cast(step) / steps; - const int nx = static_cast(std::lround(x1 + ((x2 - x1) * t))); - const int ny = static_cast(std::lround(y1 + ((y2 - y1) * t))); - SetCursorPos(nx, ny); + const int nx = static_cast( + std::lround(start.x + ((end.x - start.x) * t))); + const int ny = static_cast( + std::lround(start.y + ((end.y - start.y) * t))); + moved = SetCursorPos(nx, ny) && moved; Sleep(std::max(1, duration_ms / std::max(1, steps))); } - SendMouseButton(MOUSEEVENTF_LEFTUP); + const bool released = SendMouseButton(MOUSEEVENTF_LEFTUP); + if (!moved || !released) { + result->Error("input_failed", "Unable to complete the desktop drag."); + return; + } result->Success(EncodableValue()); return; } @@ -515,19 +606,31 @@ bool FlutterWindow::OnCreate() { int delta_y = 0; GetInt(*arguments, "deltaX", &delta_x); GetInt(*arguments, "deltaY", &delta_y); + const DisplayInfo display = + ResolveDisplay(GetString(*arguments, "displayId")); + const POINT anchor = DisplayCenterPoint(display); + if (!SetCursorPos(anchor.x, anchor.y)) { + result->Error("input_failed", "Unable to position the desktop scroll."); + return; + } + bool sent = true; if (delta_y != 0) { INPUT input{}; input.type = INPUT_MOUSE; input.mi.dwFlags = MOUSEEVENTF_WHEEL; input.mi.mouseData = static_cast(delta_y); - SendInput(1, &input, sizeof(INPUT)); + sent = SendInput(1, &input, sizeof(INPUT)) == 1 && sent; } if (delta_x != 0) { INPUT input{}; input.type = INPUT_MOUSE; input.mi.dwFlags = MOUSEEVENTF_HWHEEL; input.mi.mouseData = static_cast(delta_x); - SendInput(1, &input, sizeof(INPUT)); + sent = SendInput(1, &input, sizeof(INPUT)) == 1 && sent; + } + if (!sent) { + result->Error("input_failed", "Unable to send the desktop scroll."); + return; } result->Success(EncodableValue()); return; @@ -540,11 +643,16 @@ bool FlutterWindow::OnCreate() { } const std::wstring text = Utf8ToWide(GetString(*arguments, "text", "")); const bool press_enter = GetBool(*arguments, "pressEnter", false); + bool sent = true; if (!text.empty()) { - SendUnicodeText(text); + sent = SendUnicodeText(text); + } + if (press_enter && sent) { + sent = SendVirtualKey(VK_RETURN); } - if (press_enter) { - SendVirtualKey(VK_RETURN); + if (!sent) { + result->Error("input_failed", "Unable to send desktop text input."); + return; } result->Success(EncodableValue()); return; @@ -561,7 +669,10 @@ bool FlutterWindow::OnCreate() { result->Error("unsupported_key", "Key is not supported."); return; } - SendVirtualKey(virtual_key); + if (!SendVirtualKey(virtual_key)) { + result->Error("input_failed", "Unable to send the desktop key."); + return; + } result->Success(EncodableValue()); return; } diff --git a/lib/manager.js b/lib/manager.js index b0976e17..4fd083a9 100644 --- a/lib/manager.js +++ b/lib/manager.js @@ -1,3 +1,5 @@ +'use strict'; + const fs = require('fs'); const os = require('os'); const path = require('path'); @@ -22,7 +24,9 @@ const { PID_FILE, getDefaultVmBaseImageUrl, ensureRuntimeDirs, - migrateLegacyRuntime + migrateLegacyRuntime, + removeEnvValue: removeRuntimeEnvValue, + upsertEnvValue: upsertRuntimeEnvValue, } = require('../runtime/paths'); const { parseReleaseChannel, @@ -44,6 +48,8 @@ const { cmdMigrateDryRun, cmdMigrateRun } = require('./migrations'); +const { fetchResponseText } = require('../server/services/network/http'); +const { abortableDelay } = require('../server/utils/retry'); const APP_NAME = 'NeoAgent'; const SERVICE_LABEL = 'com.neoagent'; @@ -71,6 +77,38 @@ const COLORS = process.stdout.isTTY const CLI_INTERACTIVE = process.stdout.isTTY; const installActionItems = []; +async function fetchBoundedJsonResponse(url, init = {}, label = 'Authentication request') { + const { response, text } = await fetchResponseText(url, { + ...init, + redirect: init.redirect || 'error', + timeoutMs: 20000, + maxResponseBytes: 1024 * 1024, + serviceName: label, + timeoutCode: 'AUTH_HTTP_TIMEOUT', + tooLargeCode: 'AUTH_RESPONSE_TOO_LARGE', + }); + let data = null; + try { + data = JSON.parse(text || '{}'); + } catch { + data = null; + } + return { response, text, data }; +} + +async function fetchAuthJson(url, init = {}, label = 'Authentication request') { + const result = await fetchBoundedJsonResponse(url, init, label); + if (!result.response.ok) { + throw new Error( + `${label} failed: HTTP ${result.response.status} — ${result.text.slice(0, 500) || 'empty response'}`, + ); + } + if (!result.data || typeof result.data !== 'object') { + throw new Error(`${label} returned malformed JSON.`); + } + return result.data; +} + function logInfo(msg) { const mark = CLI_INTERACTIVE ? `${COLORS.blue}◇${COLORS.reset}` : '->'; console.log(` ${mark} ${msg}`); @@ -182,10 +220,6 @@ function sanitizeEnvKey(key) { return String(key).replace(/[\r\n]/g, ''); } -function sanitizeEnvValue(value) { - return String(value).replace(/[\r\n]/g, ''); -} - function validateEnvKey(key) { if (!/^[A-Z][A-Z0-9_]*$/.test(key)) { throw new Error(`Invalid env key "${key}". Keys must be uppercase letters, digits, and underscores (e.g. PORT, ANTHROPIC_API_KEY).`); @@ -193,33 +227,11 @@ function validateEnvKey(key) { } function upsertEnvValue(key, value) { - const safeKey = sanitizeEnvKey(key); - const safeValue = sanitizeEnvValue(value); - const raw = readEnvFileRaw(); - const lines = raw ? raw.split('\n') : []; - let replaced = false; - - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith(`${safeKey}=`)) { - lines[i] = `${safeKey}=${safeValue}`; - replaced = true; - break; - } - } - - if (!replaced) lines.push(`${safeKey}=${safeValue}`); - const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n'; - fs.writeFileSync(ENV_FILE, output, { mode: 0o600 }); + upsertRuntimeEnvValue(ENV_FILE, key, value); } function removeEnvValue(key) { - const safeKey = sanitizeEnvKey(key); - const raw = readEnvFileRaw(); - if (!raw) return false; - const lines = raw.split('\n').filter((line) => !line.startsWith(`${safeKey}=`)); - const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n'; - fs.writeFileSync(ENV_FILE, output, { mode: 0o600 }); - return true; + return removeRuntimeEnvValue(ENV_FILE, sanitizeEnvKey(key)); } function readAdminCredentials() { @@ -870,29 +882,52 @@ async function cmdMigrate(args = []) { }); } -async function pollDeviceCode({ pollUrl, pollBody, pollHeaders = {}, intervalMs, timeoutMs, onToken }) { +async function pollDeviceCode({ + pollUrl, + pollBody, + pollHeaders = {}, + intervalMs, + timeoutMs, + onToken, + signal = null, +}) { const start = Date.now(); - let currentInterval = intervalMs; + let currentInterval = Math.max(1000, Number(intervalMs) || 5000); while (Date.now() - start < timeoutMs) { - await new Promise((r) => setTimeout(r, currentInterval)); - const res = await fetch(pollUrl, { - method: 'POST', - headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', ...pollHeaders }, - body: JSON.stringify(pollBody()), - }); - if (res.status === 403 || res.status === 404) continue; - if (!res.ok) { - const text = await res.text().catch(() => 'Unknown error'); - throw new Error(`Token poll failed: HTTP ${res.status} — ${text}`); + await abortableDelay(currentInterval, signal); + let result; + try { + result = await fetchBoundedJsonResponse(pollUrl, { + method: 'POST', + headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', ...pollHeaders }, + body: JSON.stringify(pollBody()), + signal, + }, 'Device authorization poll'); + } catch (error) { + if (signal?.aborted) throw signal.reason || error; + if (/timed out|fetch failed|ECONNRESET|ECONNREFUSED|socket/i.test(String(error?.message || error))) { + continue; + } + throw error; + } + const { response, text, data } = result; + if (response.status === 403 || response.status === 404) continue; + if (!response.ok) { + throw new Error(`Token poll failed: HTTP ${response.status} — ${text.slice(0, 500)}`); + } + if (!data || typeof data !== 'object') { + throw new Error('Device authorization poll returned malformed JSON.'); } - const data = await res.json(); const done = await onToken(data); if (done) return; if (data.error === 'authorization_pending') continue; - if (data.error === 'slow_down') { currentInterval += 5000; continue; } + if (data.error === 'slow_down') { + currentInterval = Math.min(currentInterval + 5000, 30000); + continue; + } if (data.error) throw new Error(`Authentication failed: ${data.error_description || data.error}`); } - throw new Error('Authentication timed out after 15 minutes.'); + throw new Error(`Authentication timed out after ${Math.ceil(timeoutMs / 60000)} minutes.`); } async function cmdLoginClaudeCode() { @@ -958,8 +993,6 @@ async function cmdLoginClaudeCode() { const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'; - spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' }); - // Start local redirect server to capture authorization code const authCode = await new Promise((resolve, reject) => { const timeout = setTimeout(() => { @@ -983,7 +1016,7 @@ async function cmdLoginClaudeCode() { return; } - if (returnedState && returnedState !== state) { + if (returnedState !== state) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('

Authorization failed.

State mismatch. You can close this tab.

'); clearTimeout(timeout); @@ -998,7 +1031,10 @@ async function cmdLoginClaudeCode() { clearTimeout(timeout); server.close(); resolve(code); + return; } + res.writeHead(400, { 'Content-Type': 'text/html' }); + res.end('

Authorization failed.

Missing authorization code.

'); } catch (err) { res.writeHead(500); res.end('Internal error'); @@ -1007,6 +1043,7 @@ async function cmdLoginClaudeCode() { server.listen(redirectPort, 'localhost', () => { logInfo(`Waiting for OAuth callback on ${redirectUri} ...`); + spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' }); }); server.on('error', (err) => { clearTimeout(timeout); @@ -1015,7 +1052,7 @@ async function cmdLoginClaudeCode() { }); logInfo('Exchanging authorization code for access token...'); - const tokenRes = await fetch('https://platform.claude.com/v1/oauth/token', { + const tokenData = await fetchAuthJson('https://platform.claude.com/v1/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -1031,14 +1068,7 @@ async function cmdLoginClaudeCode() { scope: SCOPES, state, }), - }); - - if (!tokenRes.ok) { - const text = await tokenRes.text().catch(() => 'Unknown error'); - throw new Error(`Token exchange failed: HTTP ${tokenRes.status} — ${text}`); - } - - const tokenData = await tokenRes.json(); + }, 'Claude Code token exchange'); const accessToken = tokenData.access_token; if (!accessToken) { throw new Error('Token exchange succeeded but no access_token was returned.'); @@ -1087,8 +1117,6 @@ async function cmdLoginGrokOAuth() { const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'; - spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' }); - const authCode = await new Promise((resolve, reject) => { const timeout = setTimeout(() => { server.close(); @@ -1111,7 +1139,7 @@ async function cmdLoginGrokOAuth() { return; } - if (returnedState && returnedState !== state) { + if (returnedState !== state) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('

Authorization failed.

State mismatch. You can close this tab.

'); clearTimeout(timeout); @@ -1126,7 +1154,10 @@ async function cmdLoginGrokOAuth() { clearTimeout(timeout); server.close(); resolve(code); + return; } + res.writeHead(400, { 'Content-Type': 'text/html' }); + res.end('

Authorization failed.

Missing authorization code.

'); } catch (err) { res.writeHead(500); res.end('Internal error'); @@ -1135,6 +1166,7 @@ async function cmdLoginGrokOAuth() { server.listen(redirectPort, '127.0.0.1', () => { logInfo(`Waiting for OAuth callback on ${redirectUri} ...`); + spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' }); }); server.on('error', (err) => { clearTimeout(timeout); @@ -1143,7 +1175,7 @@ async function cmdLoginGrokOAuth() { }); logInfo('Exchanging authorization code for access token...'); - const tokenRes = await fetch('https://auth.x.ai/oauth2/token', { + const tokenData = await fetchAuthJson('https://auth.x.ai/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -1156,14 +1188,7 @@ async function cmdLoginGrokOAuth() { client_id: clientId, code_verifier: codeVerifier, }), - }); - - if (!tokenRes.ok) { - const text = await tokenRes.text().catch(() => 'Unknown error'); - throw new Error(`Token exchange failed: HTTP ${tokenRes.status} — ${text}`); - } - - const tokenData = await tokenRes.json(); + }, 'Grok OAuth token exchange'); const accessToken = tokenData.access_token; if (!accessToken) { throw new Error('Token exchange succeeded but no access_token was returned.'); @@ -1189,14 +1214,16 @@ async function cmdLogin(args = []) { const clientId = '01ab8ac9400c4e429b23'; logInfo('Requesting device code from GitHub...'); - const reqRes = await fetch('https://github.com/login/device/code', { + const deviceData = await fetchAuthJson('https://github.com/login/device/code', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: clientId, scope: 'user:email' }) - }); - if (!reqRes.ok) throw new Error(`Failed to request device code: HTTP ${reqRes.status}`); + }, 'GitHub device-code request'); - const { device_code, user_code, verification_uri, interval } = await reqRes.json(); + const { device_code, user_code, verification_uri, interval } = deviceData; + if (!device_code || !user_code || !verification_uri) { + throw new Error('GitHub device-code response was missing required fields.'); + } console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`); console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`); logInfo('Waiting for authorization (timeout in 15m)...'); @@ -1221,20 +1248,17 @@ async function cmdLogin(args = []) { const clientId = 'app_EMoamEEZ73f0CkXaXp7hrann'; logInfo('Requesting device code from OpenAI...'); - const reqRes = await fetch('https://auth.openai.com/api/accounts/deviceauth/usercode', { + const data = await fetchAuthJson('https://auth.openai.com/api/accounts/deviceauth/usercode', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: clientId, scope: 'openid profile email offline_access model.request model.read model.create' }) - }); - - if (!reqRes.ok) { - throw new Error(`Failed to request device code: HTTP ${reqRes.status}`); - } - - const data = await reqRes.json(); + }, 'OpenAI device-code request'); const { device_auth_id, interval } = data; const user_code = data.user_code || data.usercode; const verification_uri = 'https://auth.openai.com/codex/device'; + if (!device_auth_id || !user_code) { + throw new Error('OpenAI device-code response was missing required fields.'); + } console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`); console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`); @@ -1257,7 +1281,7 @@ async function cmdLogin(args = []) { }); logInfo('Exchanging authorization code for access token...'); - const exchangeRes = await fetch('https://auth.openai.com/oauth/token', { + const exchangeData = await fetchAuthJson('https://auth.openai.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ @@ -1267,14 +1291,7 @@ async function cmdLogin(args = []) { client_id: clientId, code_verifier: codeVerifier, }), - }); - - if (!exchangeRes.ok) { - const errorText = await exchangeRes.text().catch(() => 'Unknown error'); - throw new Error(`OpenAI token exchange failed: HTTP ${exchangeRes.status} — ${errorText}`); - } - - const exchangeData = await exchangeRes.json(); + }, 'OpenAI token exchange'); if (!exchangeData.access_token) { throw new Error('OpenAI token exchange succeeded but did not return an access token.'); } diff --git a/lib/schema_migrations.js b/lib/schema_migrations.js index 0452c061..b6f346f3 100644 --- a/lib/schema_migrations.js +++ b/lib/schema_migrations.js @@ -30,20 +30,41 @@ function migrateMemoryEmbeddingIndex(db) { .map((column) => column.name), ); if (!columns.has('index_version')) { - db.exec('ALTER TABLE memory_embedding_bands ADD COLUMN index_version INTEGER'); - } - db.exec(` - DROP INDEX IF EXISTS idx_memory_embedding_bands_lookup; - CREATE INDEX idx_memory_embedding_bands_lookup - ON memory_embedding_bands( - user_id, - agent_id, - dimension, - index_version, - band_index, - band_value + try { + db.exec('ALTER TABLE memory_embedding_bands ADD COLUMN index_version INTEGER'); + } catch (error) { + const refreshedColumns = new Set( + db.prepare('PRAGMA table_info(memory_embedding_bands)').all() + .map((column) => column.name), ); - `); + if (!refreshedColumns.has('index_version')) throw error; + } + } + const expectedIndexColumns = [ + 'user_id', + 'agent_id', + 'dimension', + 'index_version', + 'band_index', + 'band_value', + ]; + const indexColumns = db.prepare( + 'PRAGMA index_info(idx_memory_embedding_bands_lookup)', + ).all().map((column) => column.name); + if (indexColumns.join('\0') !== expectedIndexColumns.join('\0')) { + db.exec(` + DROP INDEX IF EXISTS idx_memory_embedding_bands_lookup; + CREATE INDEX IF NOT EXISTS idx_memory_embedding_bands_lookup + ON memory_embedding_bands( + user_id, + agent_id, + dimension, + index_version, + band_index, + band_value + ); + `); + } } function removeRetiredCaptureData(db) { @@ -661,6 +682,37 @@ function migrateAgentRunLifecycle(db) { `); } +function migrateMessagingInboundJobs(db) { + db.exec(` + CREATE TABLE IF NOT EXISTS messaging_inbound_jobs ( + id TEXT PRIMARY KEY, + message_id INTEGER NOT NULL UNIQUE, + user_id INTEGER NOT NULL, + agent_id TEXT, + platform TEXT NOT NULL, + platform_msg_id TEXT, + platform_chat_id TEXT, + payload_json TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending' + CHECK(status IN ('pending', 'processing', 'completed', 'failed')), + attempts INTEGER NOT NULL DEFAULT 0, + run_id TEXT, + last_error TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + completed_at TEXT, + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL + ); + + CREATE INDEX IF NOT EXISTS idx_messaging_inbound_jobs_pending + ON messaging_inbound_jobs(status, platform, user_id, agent_id, created_at); + CREATE INDEX IF NOT EXISTS idx_messaging_inbound_jobs_run + ON messaging_inbound_jobs(run_id); + `); +} + function runSchemaMigrations(db) { removeRetiredCaptureData(db); migrateMemoryEmbeddingIndex(db); @@ -676,6 +728,7 @@ function runSchemaMigrations(db) { migrateToolPoliciesAllowAlways(db); migrateBilling(db); migrateAgentRunLifecycle(db); + migrateMessagingInboundJobs(db); } module.exports = { @@ -694,5 +747,6 @@ module.exports = { migrateToolPoliciesAllowAlways, migrateBilling, migrateAgentRunLifecycle, + migrateMessagingInboundJobs, runSchemaMigrations, }; diff --git a/package-lock.json b/package-lock.json index 48875d86..1abc6c81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@google/generative-ai": "^0.24.0", - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.29.0", "@remotion/cli": "^4.0.459", - "@slidev/cli": "^52.15.2", + "@slidev/cli": "^52.18.0", "@slidev/theme-default": "^0.25.0", "baileys": "^6.7.21", "bcrypt": "^6.0.0", @@ -21,7 +21,7 @@ "better-sqlite3-session-store": "^0.1.0", "cheerio": "^1.0.0-rc.12", "cors": "^2.8.5", - "discord.js": "^14.25.1", + "discord.js": "^14.27.0", "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", @@ -30,9 +30,9 @@ "googleapis": "^150.0.1", "helmet": "^8.0.0", "multer": "^1.4.5-lts.1", - "node-cron": "^3.0.3", + "node-cron": "^4.6.0", "node-pty": "^1.0.0", - "nodemailer": "^8.0.5", + "nodemailer": "^9.0.3", "openai": "^4.85.4", "otplib": "^13.4.0", "playwright-chromium": "^1.59.1", @@ -42,7 +42,7 @@ "puppeteer-extra-plugin-stealth": "^2.11.2", "qrcode": "^1.5.4", "remotion": "^4.0.459", - "sharp": "^0.34.5", + "sharp": "^0.35.3", "socket.io": "^4.8.1", "stripe": "^22.2.1", "telegraf": "^4.16.3", @@ -55,8 +55,9 @@ "neoagent": "bin/neoagent.js" }, "devDependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/preset-classic": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/preset-classic": "3.10.2", + "@types/node": "^20.19.0", "autocannon": "^7.15.0", "react": "18.3.1", "react-dom": "18.3.1", @@ -64,53 +65,69 @@ "supertest": "^7.2.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.19.0" + } + }, + "node_modules/@11ty/gray-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/gray-matter/-/gray-matter-1.0.0.tgz", + "integrity": "sha512-7mJJl+wf1AByoT0PknQiQfOPnVNT4fevGrUBVWO4HXsnYn1aQPyRyrELYrNUFleUBM++KzMKN6QaxHPk0t/6/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^4.1.0", + "kind-of": "^6.0.3", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=11" } }, "node_modules/@algolia/abtesting": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.16.2.tgz", - "integrity": "sha512-n9s6bEV6imdtIEd+BGP7WkA4pEZ5YTdgQ05JQhHwWawHg3hyjpNwC0TShGz6zWhv+jfLDGA/6FFNbySFS0P9cw==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.22.0.tgz", + "integrity": "sha512-BFR6zNowNKcY7Ou7TaJc9QWexES4YKPbmf/OTFofpdsdhz4x6q0lbxp3duO0EHnyrN7rE4ba/TSXuY+BDGu4+g==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.8.tgz", - "integrity": "sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.9.tgz", + "integrity": "sha512-4U2JKLMWlDu0CotYyUkWakDxr8AIav3QtIUXXRpfavYN29aVWfzlwJp9T0rPKEf/dO2QCPAUc0Kq1Tj1GJxo2A==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.19.8", - "@algolia/autocomplete-shared": "1.19.8" + "@algolia/autocomplete-plugin-algolia-insights": "1.19.9", + "@algolia/autocomplete-shared": "1.19.9" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.8.tgz", - "integrity": "sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.9.tgz", + "integrity": "sha512-6mExC6X7762s2SV3eJy3QOkB8bdMmnUhQ2agvGVDuzwoGyr3PquGSY/0vPQXCfiAiCaXUz1rXn+lwghgSi0l0w==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.19.8" + "@algolia/autocomplete-shared": "1.19.9" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz", - "integrity": "sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.9.tgz", + "integrity": "sha512-YosP9Uoek6y/Ur1r1qeogk4biMe/hzkyNcgMCciw0//3XpCM7VlYLSHnyt/vOnEOGhCCc0+3v+unEiH6zz+Z1A==", "dev": true, "license": "MIT", "peerDependencies": { @@ -119,41 +136,41 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.50.2.tgz", - "integrity": "sha512-52iq0vHy1sphgnwoZyx5PmbEt8hsh+m7jD123LmBs6qy4GK7LbYZIeKd+nSnSipN2zvKRZ2zScS6h9PW3J7SXg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.56.0.tgz", + "integrity": "sha512-7r4Z3NC7yU1oAQVWJNA2HX7tX481F3pJvCGyLIXiTdBcthz4Q/o21jwcMYDFkuI92UWTNBQQmHYgwHo1zS5dzg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.50.2.tgz", - "integrity": "sha512-WpPIUg+cSG2aPUG0gS8Ko9DwRgbRPUZxJkolhL2aCsmSlcEEZT65dILrfg5ovcxtx0Kvr+xtBVsTMtsQWRtPDQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.56.0.tgz", + "integrity": "sha512-avmjXQSq+jadFO8Xl2em05/uQdQnEmHsJyOAdVbZkmVgpMfxL12aJwVVfGNwYr9nulcpuJN1X0lTaQ5wxuNGcA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.50.2.tgz", - "integrity": "sha512-Gj2MgtArGcsr82kIqRlo6/dCAFjrs2gLByEqyRENuT7ugrSMFuqg1vDzeBjRL1t3EJEJCFtT0PLX3gB8A6Hq4Q==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.56.0.tgz", + "integrity": "sha512-v2TPStUhY//ripPjIVclZ8AWc7DEGooXULZGFlFu37zNatgHjw34oZZ+OSbbc/YHO+xZwPl62I1k8xH1m4S2eg==", "dev": true, "license": "MIT", "engines": { @@ -161,64 +178,64 @@ } }, "node_modules/@algolia/client-insights": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.50.2.tgz", - "integrity": "sha512-CUqoid5jDpmrc0oK3/xuZXFt6kwT0P9Lw7/nsM14YTr6puvmi+OUKmURpmebQF22S2vCG8L1DAoXXujxQUi/ug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.56.0.tgz", + "integrity": "sha512-P0ehROpM4Sem3Sqo5x2cKPgj67D3G3jy0rh1Amwkcvsfr6tkvIcdCmerieanqTF7NxUMPNFLkpIFeMO8Rpa50w==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.50.2.tgz", - "integrity": "sha512-AndZWFoc0gbP5901OeQJ73BazgGgSGiBEba4ohdoJuZwHTO2Gio8Q4L1VLmytMBYcviVigB0iICToMvEJxI4ug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.56.0.tgz", + "integrity": "sha512-SXK3Vn3WVxyzbm31oePZBJkp1wpOyuWdd4B/Pv7n0aXDxmeSWhC1R1FC1517mMrFAIaPH4Rt0x6RUe7ZNjz8FA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.2.tgz", - "integrity": "sha512-NWoL+psEkz5dIzweaByVXuEB45wS8/rk0E0AhMMnaVJdVs7TcACPH2/OURm+N0xRDITkTHqCna823rd6Uqntdg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.56.0.tgz", + "integrity": "sha512-5+ZdX8garFnmycnZgKhtXHePEaLj5zqDxI/0lkhhluzCcvTn0/PvvTirTg8hHYetQHvn7GDyeAiqTAieMvMW4A==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.50.2.tgz", - "integrity": "sha512-ypSboUJ3XJoQz5DeDo82hCnrRuwq3q9ZdFhVKAik9TnZh1DvLqoQsrbBjXg7C7zQOtV/Qbge/HmyoV6V5L7MhQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.56.0.tgz", + "integrity": "sha512-+mKUdYvqOi0BcvpAEyCEw49vSBptufIcfibtHz2bdr1pI789M46Yt0uQEk/sxtK3teh71OQvVFHaTDzShUWewQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" @@ -232,87 +249,87 @@ "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.50.2", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.50.2.tgz", - "integrity": "sha512-VlR2FRXLw2bCB94SQo6zxg/Qi+547aOji6Pb+dKE7h1DMCCY317St+OpjpmgzE+bT2O9ALIc0V4nVIBOd7Gy+Q==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.56.0.tgz", + "integrity": "sha512-9g/zj+AZx5moFcdFIrYQoVrueXivjUcc3MQHtCYT8WhIuk1lUh1AyEhvJCS0XBZld09cLvd1AZ3BvDBpVpX2UA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.50.2", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.50.2.tgz", - "integrity": "sha512-Cmvfp2+qopzQt8OilU97rhLhosq7ZrB6uieok3EwFUqG/aalPg6DgfCmu0yJMrYe+KMC1qRVt1MTRAUwLknUMQ==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.56.0.tgz", + "integrity": "sha512-Qf3Sr6f9A9uxCZUf3MXS0d2b877uYzEB5yxqpVGXAhcJnBCQjrRRon0KvefpGkxy+BshrIJs96OUoMtGqXTFDA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.50.2.tgz", - "integrity": "sha512-jrkuyKoOM7dFWQ/6Y4hQAse2SC3L/RldG6GnPjMvAj65h+7Ubb51S0pKk4ofSStF0xm4LCNe0C4T6XX4nOFDiQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.56.0.tgz", + "integrity": "sha512-GXWG1rWc5wu8hY4N33Y3b6ernY6sAdAvmKWN/zHAiACOx40WnpG0TVX5YazCAr/9gOYGInSiM2A0y2jy2xbiDA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.2.tgz", - "integrity": "sha512-4107YLJqCudPiBUlwnk6oTSUVwU7ab+qL1SfQGEDYI8DZH5gsf1ekPt9JykXRKYXf2IfouFL5GiCY/PHTFIjYw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.56.0.tgz", + "integrity": "sha512-7t24cBxaInS3mZb7ddEaZT/tp6q+/aR4YttsQVyP1/i+LmwPR34atO35KjaLFCcRVrlP7sYOAqkCfg6lIRB+ew==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.50.2.tgz", - "integrity": "sha512-vOrd3MQpLgmf6wXAueTuZ/cA0W4uRwIHHaxNy3h+a6YcNn6bCV/gFdZuv3F13v593zRU2k5R75NmvRWLenvMrw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.56.0.tgz", + "integrity": "sha512-R7ePHgVYmDFjZpvrsVAfbDz/d4RxKAYZ5/vgLfIsCVRZRryjWl/3INOxpOICzitehQ5FjNtNjcLQTrmHPTcHBQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.56.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.50.2.tgz", - "integrity": "sha512-Mu9BFtgzGqDUy5Bcs2nMyoILIFSN13GKQaklKAFIsd0K3/9CpNyfeBc+/+Qs6mFZLlxG9qzullO7h+bjcTBuGQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.56.0.tgz", + "integrity": "sha512-PIOUXlSnrqM0S+WOgDRb4RzotydJH7ZoT6tOyL7tAO7qJOfvX5wsEW8Pe+PMKMwvuI4/gIyK9cg2H7lJXqnc4Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.56.0" }, "engines": { "node": ">= 14.0.0" @@ -332,15 +349,15 @@ } }, "node_modules/@antfu/ni": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-30.1.0.tgz", - "integrity": "sha512-3VuAbPjgY52rQNn4wABaXMhBU2Oq91uy6L8nX49eJ35OLI68CyckGU+HZxcaHix4ymuGM2nFL1D6sLpgODK5xw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-30.3.0.tgz", + "integrity": "sha512-KkYFVPA1IvC3m+WBKUuY5oxYc4OdXr83jvnlOn8a+bJM10yQMu+slFN/1pDzAw2JZVi+FYP9IYjo4KaPIccMmA==", "license": "MIT", "dependencies": { "fzf": "^0.5.2", - "package-manager-detector": "^1.6.0", - "tinyexec": "^1.0.4", - "tinyglobby": "^0.2.15" + "package-manager-detector": "^1.7.0", + "tinyexec": "^1.2.4", + "tinyglobby": "^0.2.17" }, "bin": { "na": "bin/na.mjs", @@ -380,6 +397,21 @@ "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/@assemblyscript/loader": { "version": "0.19.23", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.23.tgz", @@ -440,29 +472,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/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/@babel/core/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/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -489,12 +498,12 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -516,15 +525,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -535,17 +535,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "engines": { @@ -565,13 +565,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, @@ -609,31 +609,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/helper-define-polyfill-provider/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==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -644,13 +619,13 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -687,36 +662,36 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -726,14 +701,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -743,13 +718,13 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -783,15 +758,15 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -826,14 +801,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -843,13 +818,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -859,13 +834,30 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -875,15 +867,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -893,14 +885,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -936,13 +928,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -952,13 +944,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -968,12 +960,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -983,12 +975,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1015,13 +1007,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1031,15 +1023,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1049,15 +1041,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1067,13 +1059,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1083,13 +1075,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1099,14 +1091,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1116,14 +1108,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1133,18 +1125,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1154,14 +1146,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1171,14 +1163,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1188,14 +1180,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1205,13 +1197,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1221,14 +1213,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1238,13 +1230,13 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1254,14 +1246,14 @@ } }, "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1271,13 +1263,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1287,13 +1279,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1303,14 +1295,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1320,15 +1312,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1338,13 +1330,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1354,13 +1346,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1370,13 +1362,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1386,13 +1378,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1402,14 +1394,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1419,14 +1411,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1436,16 +1428,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1455,14 +1447,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1472,14 +1464,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1489,13 +1481,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1505,13 +1497,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1521,13 +1513,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1537,17 +1529,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1557,14 +1549,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1574,13 +1566,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1590,14 +1582,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1607,13 +1599,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1623,14 +1615,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1640,15 +1632,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1658,13 +1650,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1674,13 +1666,13 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", - "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.29.7.tgz", + "integrity": "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1690,13 +1682,13 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1706,17 +1698,17 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", - "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-syntax-jsx": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1726,13 +1718,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" + "@babel/plugin-transform-react-jsx": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1742,14 +1734,14 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1759,13 +1751,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", + "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1775,14 +1767,14 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1792,13 +1784,13 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1808,14 +1800,14 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", - "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -1839,13 +1831,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1855,14 +1847,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", + "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1872,13 +1864,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1888,13 +1880,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1904,13 +1896,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1920,16 +1912,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1939,13 +1931,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1955,14 +1947,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1972,14 +1964,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1989,14 +1981,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2006,76 +1998,77 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", @@ -2130,18 +2123,18 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", - "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.28.0", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2151,17 +2144,17 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2171,9 +2164,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, "license": "MIT", "engines": { @@ -2212,40 +2205,17 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/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/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "node": ">=6.9.0" } }, "node_modules/@borewit/text-codec": { @@ -2265,12 +2235,12 @@ "license": "MIT" }, "node_modules/@cacheable/memory": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.8.tgz", - "integrity": "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", "license": "MIT", "dependencies": { - "@cacheable/utils": "^2.4.0", + "@cacheable/utils": "^2.5.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" @@ -2291,12 +2261,12 @@ } }, "node_modules/@cacheable/utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.0.tgz", - "integrity": "sha512-PeMMsqjVq+bF0WBsxFBxr/WozBJiZKY0rUojuaCoIaKnEl3Ju1wfEwS+SV1DU/cSe8fqHIPiYJFif8T3MVt4cQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", "license": "MIT", "dependencies": { - "hashery": "^1.5.0", + "hashery": "^1.5.1", "keyv": "^5.6.0" } }, @@ -2577,9 +2547,9 @@ } }, "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -3014,9 +2984,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -3479,9 +3449,9 @@ } }, "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -3703,1932 +3673,1399 @@ } }, "node_modules/@devframes/hub": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@devframes/hub/-/hub-0.5.4.tgz", - "integrity": "sha512-NZs5RFuNb6tjQd2JBsRYQT+OqE+z16Kg9/72HG4k+uJdzK90sAGtAjOwK8bsvav/9l85KGx4agfkgxnfbl313w==", + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@devframes/hub/-/hub-0.7.9.tgz", + "integrity": "sha512-+tARSZfwkzez6RKA8ShlntAmfKTn7Z+IM2xwDilNMze5YO+E4n/WmaJBYZL2VvXdDiWB0cJbbwHto+vsrEQB9g==", "license": "MIT", "dependencies": { "birpc": "^4.0.0", - "nostics": "^0.2.0", + "destr": "^2.0.5", + "nostics": "^1.2.0", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", - "tinyexec": "^1.2.2" + "tinyexec": "^1.2.4", + "zigpty": "^0.2.1" }, "peerDependencies": { - "devframe": "0.5.4" + "devframe": "0.7.9" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.132.0.tgz", - "integrity": "sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw==", - "cpu": [ - "arm" - ], + "node_modules/@devframes/json-render": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@devframes/json-render/-/json-render-0.7.9.tgz", + "integrity": "sha512-VgUBkxyCG60wTMvihZpGwX9RZwhmhNf+znh/R0Dlwz602E74HtCD2J8jdWqKje4aia4KmmHFLLwWC/itmkfJZg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@json-render/core": "^0.19.0", + "nostics": "^1.2.0", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@devframes/hub": "0.7.9", + "devframe": "0.7.9" + }, + "peerDependenciesMeta": { + "@devframes/hub": { + "optional": true + } } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.132.0.tgz", - "integrity": "sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg==", - "cpu": [ - "arm64" - ], + "node_modules/@devframes/json-render/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.132.0.tgz", - "integrity": "sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@discordjs/builders": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz", + "integrity": "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.2", + "@discordjs/util": "^1.2.0", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.40", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.132.0.tgz", - "integrity": "sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16.11.0" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.132.0.tgz", - "integrity": "sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@discordjs/formatters": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", + "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.132.0.tgz", - "integrity": "sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@discordjs/rest": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.3.tgz", + "integrity": "sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.2.0", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.5", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.50", + "magic-bytes.js": "^1.13.0", + "tslib": "^2.6.3", + "undici": "^6.27.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.132.0.tgz", - "integrity": "sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.132.0.tgz", - "integrity": "sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@discordjs/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", + "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.132.0.tgz", - "integrity": "sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.132.0.tgz", - "integrity": "sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.132.0.tgz", - "integrity": "sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA==", - "cpu": [ - "riscv64" - ], + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=10.0.0" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.132.0.tgz", - "integrity": "sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg==", - "cpu": [ - "riscv64" - ], + "node_modules/@docsearch/core": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.6.3.tgz", + "integrity": "sha512-rUOujwIpxJRgD7+kicVsI3D5sqBvdiRTquzWBpTEXZs8ZXfGbfzpus5HqumaNYTppN2HvH8E2yNuRwYdHJeOlA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.132.0.tgz", - "integrity": "sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.132.0.tgz", - "integrity": "sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "node_modules/@docsearch/css": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.3.tgz", + "integrity": "sha512-nlOwcXcsNAptQl4vlL4MA78qNJKO0Qlds5GuBjCoePgkebTXLSf8Qt1oyZ3YBshYupKXG9VRGEsk1zr23d+bzQ==", + "dev": true, + "license": "MIT" }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.132.0.tgz", - "integrity": "sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ==", - "cpu": [ - "x64" - ], + "node_modules/@docsearch/react": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.6.3.tgz", + "integrity": "sha512-Bg2wdDsoQVlNCcEKuEJAU04tvHCqgx8rIu+uIoM4pRtcx3TBKJuXutJik3LTA8LRc9YEyHkrYUrmcC0D7BYf+g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/core": "4.6.3", + "@docsearch/css": "4.6.3" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.132.0.tgz", - "integrity": "sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ==", - "cpu": [ - "arm64" - ], + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.132.0.tgz", - "integrity": "sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw==", - "cpu": [ - "wasm32" - ], + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@algolia/autocomplete-shared": "1.19.2" }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependencies": { + "search-insights": ">= 1 < 3" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.132.0.tgz", - "integrity": "sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw==", - "cpu": [ - "arm64" - ], + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.132.0.tgz", - "integrity": "sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA==", - "cpu": [ - "ia32" - ], + "node_modules/@docusaurus/babel": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.2.tgz", + "integrity": "sha512-aJ1hpGyvfkte3dDAfNbWM4biW4yWZBVz7TIGLZP+v+tWOBgxX3e0N5ZIXHIvmfNNXTI77pcHUx3KmtOk05Ze3Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20.0" } }, - "node_modules/@devframes/hub/node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.132.0.tgz", - "integrity": "sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ==", - "cpu": [ - "x64" - ], + "node_modules/@docusaurus/bundler": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.2.tgz", + "integrity": "sha512-i0ZNcy0f0WhaOlYVgzLsWhIoEXO9kS3HRoKPtgE6vQtZUq7arKZaYdNBudr3mqCmd+TyOkwtwfHgs1ENj07r5g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.10.2", + "@docusaurus/cssnano-preset": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^7.0.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } } }, - "node_modules/@devframes/hub/node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "node_modules/@docusaurus/core": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.2.tgz", + "integrity": "sha512-EYByj6nk+aD9KeVxV6Hmo2/nAAT79P21Y82ycTBOBtrmqilloIbIEhgL2/8Xpt2Jz/pgNqHAwyusOGwmbKeJmA==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "dependencies": { + "@docusaurus/babel": "3.10.2", + "@docusaurus/bundler": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "detect-port": "^2.1.0", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "execa": "^5.1.1", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "open": "^8.4.0", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.3", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.7", + "tinypool": "^1.0.2", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^5.2.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*", + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } } }, - "node_modules/@devframes/hub/node_modules/nostics": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nostics/-/nostics-0.2.0.tgz", - "integrity": "sha512-/WQpI46UMbqvy1okYb+V+9wW3J8/m6GJ33wm691n/tyi6YtJiZ6ssJjENAU7y4evfYrrgYN9HllKDzPvffil1w==", + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.2.tgz", + "integrity": "sha512-4gCnHRbJLTloiwfvFAa92tgb2gI4KYhvjfQVYnEaiMO/EgvWfCo1LwytHXen+1oZAN0VAlS0JAPxp3MsvKDa3A==", + "dev": true, "license": "MIT", "dependencies": { - "magic-string": "^0.30.21", - "oxc-parser": "^0.132.0", - "unplugin": "^3.0.0" + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.5.4", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" } }, - "node_modules/@devframes/hub/node_modules/oxc-parser": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.132.0.tgz", - "integrity": "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg==", + "node_modules/@docusaurus/logger": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.2.tgz", + "integrity": "sha512-gSEwqtPfCAnC3ZSJY6xL7tcIfgg0vFD39jbv93eakuweyvO2864xR0K+kmKwBhkTCtWRNjuGGnb5rdmkD/ndqw==", + "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.132.0" - }, + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.2.tgz", + "integrity": "sha512-9Fd4V/SFjfrVQ0JH5EN0+iPWyFunvTeQE3gfyFeetqPaXMP0OylIjOw16dCuXG4NZJrYdBqwzjh18/h3gRi47w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" }, - "funding": { - "url": "https://github.com/sponsors/Boshen" + "engines": { + "node": ">=20.0" }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.132.0", - "@oxc-parser/binding-android-arm64": "0.132.0", - "@oxc-parser/binding-darwin-arm64": "0.132.0", - "@oxc-parser/binding-darwin-x64": "0.132.0", - "@oxc-parser/binding-freebsd-x64": "0.132.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", - "@oxc-parser/binding-linux-arm64-musl": "0.132.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", - "@oxc-parser/binding-linux-x64-gnu": "0.132.0", - "@oxc-parser/binding-linux-x64-musl": "0.132.0", - "@oxc-parser/binding-openharmony-arm64": "0.132.0", - "@oxc-parser/binding-wasm32-wasi": "0.132.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", - "@oxc-parser/binding-win32-x64-msvc": "0.132.0" - } - }, - "node_modules/@devframes/hub/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.2.tgz", + "integrity": "sha512-h/I5e4jaAhDHW4vaLENi1i2hnOEnXY1t9R+nnRTbgUl7ymVRzN/HF7dDfj8rKYGj8gfIge+Ef+iYRAMtbGvsrQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@docusaurus/types": "3.10.2", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "react": "*", + "react-dom": "*" } }, - "node_modules/@devframes/hub/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.2.tgz", + "integrity": "sha512-0cbEnNKf0InmLkhj/+nVRmqEnWEoOE8Mh+2x1qOXI0qYpCnphq4RXknVJ8BvybKRXqYVvbmdMfiJSup+k4tm5w==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "cheerio": "1.0.0-rc.12", + "combine-promises": "^1.1.0", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@discordjs/builders": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.0.tgz", - "integrity": "sha512-7pVKxVWkeLUtrTo9nTYkjRcJk0Hlms6lYervXAD7E7+K5lil9ms2JrEB1TalMiHvQMh7h1HJZ4fCJa0/vHpl4w==", - "license": "Apache-2.0", + "node_modules/@docusaurus/plugin-content-blog/node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@discordjs/formatters": "^0.6.2", - "@discordjs/util": "^1.2.0", - "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "^0.38.40", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.4", - "tslib": "^2.6.3" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" }, "engines": { - "node": ">=16.11.0" + "node": ">= 6" }, "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, - "node_modules/@discordjs/collection": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", - "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.11.0" + "node_modules/@docusaurus/plugin-content-blog/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" } }, - "node_modules/@discordjs/formatters": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", - "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", - "license": "Apache-2.0", + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.2.tgz", + "integrity": "sha512-Sqwl4FPoZBDrlY8I2VU2H8O0M91CHp9T8ToMSkTZmjvHCif+1laqfXi6sTk8IfyVS/trN5yNjcWd1bFsGB6W5Q==", + "dev": true, + "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.33" + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" }, "engines": { - "node": ">=16.11.0" + "node": ">=20.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@discordjs/rest": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.1.tgz", - "integrity": "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==", - "license": "Apache-2.0", + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.2.tgz", + "integrity": "sha512-h5R12sZ/vV9EPiVjvIl9YFCOwkpwXes7dQMYt3EvP6Pphu4amHxxTqWxf08Fl5DR8h+oZMbWpFTNw5vKEYfvzQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@discordjs/collection": "^2.1.1", - "@discordjs/util": "^1.2.0", - "@sapphire/async-queue": "^1.5.3", - "@sapphire/snowflake": "^3.5.5", - "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.38.40", - "magic-bytes.js": "^1.13.0", - "tslib": "^2.6.3", - "undici": "6.24.1" + "@docusaurus/core": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" }, "engines": { - "node": ">=18" + "node": ">=20.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.2.tgz", + "integrity": "sha512-UkdvQby5OQUKWrw3lLnSTJXQ6VETaUVTuPQX9AABtmFm5h+ifEBx1OQ+LN726Q4byuwBf2ElHkf4qU4hTxdvRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "engines": { + "node": ">=20.0" } }, - "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz", - "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==", + "node_modules/@docusaurus/plugin-debug": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.2.tgz", + "integrity": "sha512-8vbZNOSCpnsT57EY6CgN7sgRVmx3KTYwO8Uvo2pbxOyb8tbqAwtT9SslqaQ41HbA1v1hpn5RP7u5s2KvRwAFpQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^2.3.0", + "tslib": "^2.6.0" + }, "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@discordjs/util": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", - "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", - "license": "Apache-2.0", + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.2.tgz", + "integrity": "sha512-kMHMBK9j4VAtgd5owwrRLRIi0EjkrpXlX7ePj1+y68XfVZV9I1T4S+koPDm+Hfw2TtnyHvh0uNrDvjz+DjQGVA==", + "dev": true, + "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.33" + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" }, "engines": { - "node": ">=18" + "node": ">=20.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@discordjs/ws": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", - "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", - "license": "Apache-2.0", + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.2.tgz", + "integrity": "sha512-Vt90nNFhtAChRe9+it1hcHFgFvETdSnOkL5Bma+p6E/yU2tAYrvvyk+gv+LJGM2ZUkyKuKXLRsZ2Lb0bO7+Vog==", + "dev": true, + "license": "MIT", "dependencies": { - "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.5.1", - "@discordjs/util": "^1.1.0", - "@sapphire/async-queue": "^1.5.2", - "@types/ws": "^8.5.10", - "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "^0.38.1", - "tslib": "^2.6.2", - "ws": "^8.17.0" + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" }, "engines": { - "node": ">=16.11.0" + "node": ">=20.0" }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.2.tgz", + "integrity": "sha512-MLCffCldysi/R0nzJQP7ZWd0xAoGNnSTiVOo6TTR6mKVGFhE+/XArGe67ZcaZv1uytgQXoXs92VJrgVDrz80rQ==", "dev": true, "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/core": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.6.2.tgz", - "integrity": "sha512-/S0e6Dj7Zcm8m9Rru49YEX49dhU11be68c+S/BCyN8zQsTTgkKzXlhRbVL5mV6lOLC2+ZRRryaTdcm070Ug2oA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0" + "node": ">=20.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docsearch/css": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.2.tgz", - "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@docsearch/react": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.6.2.tgz", - "integrity": "sha512-/BbtGFtqVOGwZx0dw/UfhN/0/DmMQYnulY4iv0tPRhC2JCXv0ka/+izwt3Jzo1ZxXS/2eMvv9zHsBJOK1I9f/w==", + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.2.tgz", + "integrity": "sha512-PODkwg5XetLML3hU/3xpCKJUZ9cqExLaBnD/Fzzwj2VHogLeqnDisLIujae87zuze7T4mCm2A6KEqZkyiz07EQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-core": "1.19.2", - "@docsearch/core": "4.6.2", - "@docsearch/css": "4.6.2" + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0", - "search-insights": ">= 1 < 3" + "engines": { + "node": ">=20.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", - "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", - "@algolia/autocomplete-shared": "1.19.2" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", - "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.2.tgz", + "integrity": "sha512-JgfT3jWM0TJ8Uw0cEcqxHpybngQY1vlBYpuuNO+gEh5iPh5Ar+vxq/u9CFrYsWeXy48BN7Db76Pzp2edNXUQ8A==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.19.2" + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" }, "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", - "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/babel": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.0.tgz", - "integrity": "sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==", + "node_modules/@docusaurus/preset-classic": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.2.tgz", + "integrity": "sha512-a4B3VczmDl99zK0EufDQYomdJ186WDingjmDXxhN2PNPS9Ty/Y2M5CLFX1KQMRKqRTLiRDKfutzG5IY1FC/ceg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.25.9", - "@babel/preset-react": "^7.25.9", - "@babel/preset-typescript": "^7.25.9", - "@babel/runtime": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.10.0", - "@docusaurus/utils": "3.10.0", - "babel-plugin-dynamic-import-node": "^2.3.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" + "@docusaurus/core": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/plugin-content-pages": "3.10.2", + "@docusaurus/plugin-css-cascade-layers": "3.10.2", + "@docusaurus/plugin-debug": "3.10.2", + "@docusaurus/plugin-google-analytics": "3.10.2", + "@docusaurus/plugin-google-gtag": "3.10.2", + "@docusaurus/plugin-google-tag-manager": "3.10.2", + "@docusaurus/plugin-sitemap": "3.10.2", + "@docusaurus/plugin-svgr": "3.10.2", + "@docusaurus/theme-classic": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-search-algolia": "3.10.2", + "@docusaurus/types": "3.10.2" }, "engines": { "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/babel/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "node_modules/@docusaurus/theme-classic": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.2.tgz", + "integrity": "sha512-JqTSLQmqmA9uKWZsD5iwBGJ4JyKB4/yTw6PsSXVPRJG/6GAm/u+add9Iip+hvwP12/AnPNztrdxsI14NJW4KeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/plugin-content-pages": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-translations": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "copy-text-to-clipboard": "^3.2.0", + "infima": "0.2.0-alpha.45", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.5.4", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" }, "engines": { - "node": ">=14.14" + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/bundler": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.0.tgz", - "integrity": "sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==", + "node_modules/@docusaurus/theme-common": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.2.tgz", + "integrity": "sha512-R9b/vMpK1yye6hNZTA6x/ivRv+at6GhxnXcxkpzCGzO1R1RwiquqiFg2wMFh6aqlJTpWRFKpFD2TzCDQcyOU0A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.10.0", - "@docusaurus/cssnano-preset": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "babel-loader": "^9.2.1", - "clean-css": "^5.3.3", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "file-loader": "^6.2.0", - "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.2", - "null-loader": "^4.0.1", - "postcss": "^8.5.4", - "postcss-loader": "^7.3.4", - "postcss-preset-env": "^10.2.1", - "terser-webpack-plugin": "^5.3.9", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "webpack": "^5.95.0", - "webpackbar": "^6.0.1" + "utility-types": "^3.10.0" }, "engines": { "node": ">=20.0" }, "peerDependencies": { - "@docusaurus/faster": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/core": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.0.tgz", - "integrity": "sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==", + "node_modules/@docusaurus/theme-search-algolia": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.2.tgz", + "integrity": "sha512-1msxllyhi/5m77JukXtp5UFnUAriwZIC1oJ7MTnpQpCwLTbclJi5BK5n28CTZuSXpQN2ewbbnqRgAhMM6c6ihg==", "dev": true, "license": "MIT", "dependencies": { - "@docusaurus/babel": "3.10.0", - "@docusaurus/bundler": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "core-js": "^3.31.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", + "@algolia/autocomplete-core": "^1.19.2", + "@docsearch/react": "^3.9.0 || ^4.3.2", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-translations": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "algoliasearch": "^5.37.0", + "algoliasearch-helper": "^3.26.0", + "clsx": "^2.0.0", "eta": "^2.2.0", - "eval": "^0.1.8", - "execa": "^5.1.1", "fs-extra": "^11.1.1", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.6.0", - "leven": "^3.1.0", "lodash": "^4.17.21", - "open": "^8.4.0", - "p-map": "^4.0.0", - "prompts": "^2.4.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.3", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.7", - "tinypool": "^1.0.2", "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "webpack": "^5.95.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^5.2.2", - "webpack-merge": "^6.0.1" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" + "utility-types": "^3.10.0" }, "engines": { "node": ">=20.0" }, "peerDependencies": { - "@docusaurus/faster": "*", - "@mdx-js/react": "^3.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } } }, - "node_modules/@docusaurus/core/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "node_modules/@docusaurus/theme-translations": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.2.tgz", + "integrity": "sha512-iv20wrxnyXkY89LM3TzRlzGlt5fIGO5UnaR6UL1ZVfB9RRFjxQFQ6awDrwAc6Km8Y5gD8pInuwYPF+6/TiCxXA==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" }, "engines": { - "node": ">=14.14" + "node": ">=20.0" } }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz", - "integrity": "sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==", + "node_modules/@docusaurus/types": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.2.tgz", + "integrity": "sha512-B6rvfwIFSapUqUJjMriZswX13K8l5Z7AcmVE6uTEJpYddQieSTR12DsGaFtcZAIDsQd4p+0WTl0Vc6jmZK0Trw==", "dev": true, "license": "MIT", "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.5.4", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/mdast": "^4.0.2", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.95.0", + "webpack-merge": "^5.9.0" }, - "engines": { - "node": ">=20.0" + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/logger": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.0.tgz", - "integrity": "sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==", + "node_modules/@docusaurus/types/node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "engines": { - "node": ">=20.0" + "node": ">=6" } }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz", - "integrity": "sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==", + "node_modules/@docusaurus/types/node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^2.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" + "kind-of": "^6.0.2" }, "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=8" } }, - "node_modules/@docusaurus/mdx-loader/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" }, "engines": { - "node": ">=14.14" - } - }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz", - "integrity": "sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.10.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" + "node": ">=10.0.0" } }, - "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz", - "integrity": "sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==", + "node_modules/@docusaurus/utils": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.2.tgz", + "integrity": "sha512-xx0W3eav2uW1NRIpuHJWNwLTC15xPNjU4Uxi9NSnd3swYC96BE3vFiT93SD8s24kmAAWNwgZwfZ2fghGZ01Lcw==", "dev": true, "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "cheerio": "1.0.0-rc.12", - "combine-promises": "^1.1.0", - "feed": "^4.2.2", + "@11ty/gray-matter": "^1.0.0", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "escape-string-regexp": "^4.0.0", + "execa": "^5.1.1", + "file-loader": "^6.2.0", "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "srcset": "^4.0.0", + "micromatch": "^4.0.5", + "p-queue": "^6.6.2", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", "utility-types": "^3.10.0", "webpack": "^5.88.1" }, "engines": { "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/plugin-content-blog/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "node_modules/@docusaurus/utils-common": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.2.tgz", + "integrity": "sha512-x3Dz6jv6iQKBNjBmVTu8p57abMp/VNTUgKBMgRVXJc5444orBTsArv0+cdfrXTiz/VMmHfDRVkPbL7GH2B7T7w==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@docusaurus/types": "3.10.2", + "tslib": "^2.6.0" }, "engines": { - "node": ">=14.14" + "node": ">=20.0" } }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz", - "integrity": "sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==", + "node_modules/@docusaurus/utils-validation": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.2.tgz", + "integrity": "sha512-sn8unbDfUL585NtR3cwHefPicOyaHvPaX7VD0aOg/siIxUBoKyKKaGEqzJZDS64mM43TnxurkYDtmB1wsJlZsw==", "dev": true, "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "@types/react-router-config": "^5.0.7", - "combine-promises": "^1.1.0", - "fs-extra": "^11.1.1", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "tslib": "^2.6.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" + "tslib": "^2.6.0" }, "engines": { "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, + "node_modules/@drauu/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@drauu/core/-/core-1.0.0.tgz", + "integrity": "sha512-r1fPyuKaGuNHc8vxRFUT8LxqWjJ3nx+U+zsHcEOurmJoB7uN+zpFw5kTLInfdfvQZ+qF/ebQjw1AwbGcc1XKsQ==", "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz", - "integrity": "sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==", - "dev": true, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", "license": "MIT", + "optional": true, "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@docusaurus/plugin-content-pages/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", "license": "MIT", + "optional": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" + "tslib": "^2.4.0" } }, - "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz", - "integrity": "sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==", - "dev": true, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", + "optional": true, "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" + "tslib": "^2.4.0" } }, - "node_modules/@docusaurus/plugin-debug": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz", - "integrity": "sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==", - "dev": true, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "fs-extra": "^11.1.1", - "react-json-view-lite": "^2.3.0", - "tslib": "^2.6.0" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-debug/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=14.14" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz", - "integrity": "sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==", - "dev": true, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "tslib": "^2.6.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz", - "integrity": "sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==", - "dev": true, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "@types/gtag.js": "^0.0.20", - "tslib": "^2.6.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz", - "integrity": "sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==", - "dev": true, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "tslib": "^2.6.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz", - "integrity": "sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==", - "dev": true, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-sitemap/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=14.14" + "node": ">=18" } }, - "node_modules/@docusaurus/plugin-svgr": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz", - "integrity": "sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==", - "dev": true, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "@svgr/core": "8.1.0", - "@svgr/webpack": "^8.1.0", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/preset-classic": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz", - "integrity": "sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==", - "dev": true, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/plugin-content-blog": "3.10.0", - "@docusaurus/plugin-content-docs": "3.10.0", - "@docusaurus/plugin-content-pages": "3.10.0", - "@docusaurus/plugin-css-cascade-layers": "3.10.0", - "@docusaurus/plugin-debug": "3.10.0", - "@docusaurus/plugin-google-analytics": "3.10.0", - "@docusaurus/plugin-google-gtag": "3.10.0", - "@docusaurus/plugin-google-tag-manager": "3.10.0", - "@docusaurus/plugin-sitemap": "3.10.0", - "@docusaurus/plugin-svgr": "3.10.0", - "@docusaurus/theme-classic": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/theme-search-algolia": "3.10.0", - "@docusaurus/types": "3.10.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-classic": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz", - "integrity": "sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/plugin-content-blog": "3.10.0", - "@docusaurus/plugin-content-docs": "3.10.0", - "@docusaurus/plugin-content-pages": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/theme-translations": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.45", - "lodash": "^4.17.21", - "nprogress": "^0.2.0", - "postcss": "^8.5.4", - "prism-react-renderer": "^2.3.0", - "prismjs": "^1.29.0", - "react-router-dom": "^5.3.4", - "rtlcss": "^4.1.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/theme-common": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.0.tgz", - "integrity": "sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==", - "dev": true, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@docusaurus/mdx-loader": "3.10.0", - "@docusaurus/module-type-aliases": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "clsx": "^2.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^2.3.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz", - "integrity": "sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==", - "dev": true, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@algolia/autocomplete-core": "^1.19.2", - "@docsearch/react": "^3.9.0 || ^4.3.2", - "@docusaurus/core": "3.10.0", - "@docusaurus/logger": "3.10.0", - "@docusaurus/plugin-content-docs": "3.10.0", - "@docusaurus/theme-common": "3.10.0", - "@docusaurus/theme-translations": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-validation": "3.10.0", - "algoliasearch": "^5.37.0", - "algoliasearch-helper": "^3.26.0", - "clsx": "^2.0.0", - "eta": "^2.2.0", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "node": ">=18" } }, - "node_modules/@docusaurus/theme-search-algolia/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.14" + "node": ">=18" } }, - "node_modules/@docusaurus/theme-translations": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz", - "integrity": "sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/theme-translations/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@docusaurus/types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.0.tgz", - "integrity": "sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/types/node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@docusaurus/types/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@docusaurus/types/node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@docusaurus/types/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/utils": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.0.tgz", - "integrity": "sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.10.0", - "@docusaurus/types": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "escape-string-regexp": "^4.0.0", - "execa": "^5.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "github-slugger": "^1.5.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "jiti": "^1.20.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "p-queue": "^6.6.2", - "prompts": "^2.4.2", - "resolve-pathname": "^3.0.0", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-common": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.0.tgz", - "integrity": "sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.10.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz", - "integrity": "sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.10.0", - "@docusaurus/utils": "3.10.0", - "@docusaurus/utils-common": "3.10.0", - "fs-extra": "^11.2.0", - "joi": "^17.9.2", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-validation/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@docusaurus/utils/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@drauu/core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@drauu/core/-/core-1.0.0.tgz", - "integrity": "sha512-r1fPyuKaGuNHc8vxRFUT8LxqWjJ3nx+U+zsHcEOurmJoB7uN+zpFw5kTLInfdfvQZ+qF/ebQjw1AwbGcc1XKsQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@esbuild/aix-ppc64": { + "node_modules/@esbuild/linux-ppc64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], "license": "MIT", "optional": true, "os": [ - "aix" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm": { + "node_modules/@esbuild/linux-riscv64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ - "arm" + "riscv64" ], "license": "MIT", "optional": true, "os": [ - "android" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm64": { + "node_modules/@esbuild/linux-s390x": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ - "arm64" + "s390x" ], "license": "MIT", "optional": true, "os": [ - "android" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-x64": { + "node_modules/@esbuild/linux-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ - "android" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/darwin-arm64": { + "node_modules/@esbuild/netbsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -5769,14 +5206,6 @@ "node": ">=18" } }, - "node_modules/@eshaz/web-worker": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@eshaz/web-worker/-/web-worker-1.2.2.tgz", - "integrity": "sha512-WxXiHFmD9u/owrzempiDlBB1ZYqiLnm9s6aPc8AlFQalq2tKmqdmMr9GXOupDgzXtqnBipj8Un0gkIm7Sjf8mw==", - "license": "Apache-2.0", - "optional": true, - "peer": true - }, "node_modules/@fix-webm-duration/fix": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@fix-webm-duration/fix/-/fix-1.0.1.tgz", @@ -5797,12 +5226,12 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { @@ -5815,9 +5244,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@google/generative-ai": { @@ -5855,21 +5284,21 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.13", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", - "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.11.tgz", + "integrity": "sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA==", "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { "hono": "^4" } }, "node_modules/@iconify-json/carbon": { - "version": "1.2.23", - "resolved": "https://registry.npmjs.org/@iconify-json/carbon/-/carbon-1.2.23.tgz", - "integrity": "sha512-7apXetbRmEiWDXIQyikFJZyq7pCVBKHYRzmeLdtT7wWoHYdWHwnFcBAzpuLoSh+ZEAfXZapSWEe8iuS6dUqf+Q==", + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@iconify-json/carbon/-/carbon-1.2.24.tgz", + "integrity": "sha512-b7u/eTWE3xFa7UXlRJBSEm6At1y6+F0P3imBEip5/8QeRzouxVjBf80Y2xnu1GYsFo9MYlHA0bZfxxMd5givfw==", "license": "Apache-2.0", "dependencies": { "@iconify/types": "*" @@ -5900,9 +5329,9 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", - "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^1.1.0", @@ -5920,9 +5349,9 @@ } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", "cpu": [ "arm64" ], @@ -5932,19 +5361,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", "cpu": [ "x64" ], @@ -5954,19 +5383,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", "cpu": [ "arm64" ], @@ -5980,9 +5428,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", "cpu": [ "x64" ], @@ -5996,9 +5444,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", "cpu": [ "arm" ], @@ -6012,9 +5460,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", "cpu": [ "arm64" ], @@ -6028,9 +5476,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", "cpu": [ "ppc64" ], @@ -6044,9 +5492,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", "cpu": [ "riscv64" ], @@ -6060,9 +5508,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", "cpu": [ "s390x" ], @@ -6076,9 +5524,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", "cpu": [ "x64" ], @@ -6092,9 +5540,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", "cpu": [ "arm64" ], @@ -6108,9 +5556,9 @@ } }, "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", "cpu": [ "x64" ], @@ -6124,9 +5572,9 @@ } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", "cpu": [ "arm" ], @@ -6136,19 +5584,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", "cpu": [ "arm64" ], @@ -6158,19 +5606,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", "cpu": [ "ppc64" ], @@ -6180,19 +5628,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", "cpu": [ "riscv64" ], @@ -6202,19 +5650,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", "cpu": [ "s390x" ], @@ -6224,19 +5672,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", "cpu": [ "x64" ], @@ -6246,19 +5694,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", "cpu": [ "arm64" ], @@ -6268,19 +5716,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" } }, "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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", "cpu": [ "x64" ], @@ -6290,38 +5738,54 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" } }, "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" - ], + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@emnapi/runtime": "^1.11.1" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.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==", + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", "cpu": [ "arm64" ], @@ -6331,16 +5795,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", "cpu": [ "ia32" ], @@ -6350,16 +5814,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.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==", + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", "cpu": [ "x64" ], @@ -6369,12 +5833,56 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.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/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/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -6461,6 +5969,27 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@json-render/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@json-render/core/-/core-0.19.0.tgz", + "integrity": "sha512-vvcyZ+10EDZKbEyB1J2kXOGfDaiZR2LurZGSqi2r5STHyKr+Te85DWaBxTwRGgM7U1LtIvNx85BzzjElRKoAIg==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.3.6" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@json-render/core/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", @@ -6513,14 +6042,14 @@ } }, "node_modules/@jsonjoy.com/fs-core": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", - "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.64.0.tgz", + "integrity": "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "engines": { @@ -6535,15 +6064,15 @@ } }, "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", - "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.64.0.tgz", + "integrity": "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "engines": { @@ -6558,17 +6087,17 @@ } }, "node_modules/@jsonjoy.com/fs-node": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", - "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.64.0.tgz", + "integrity": "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", - "@jsonjoy.com/fs-print": "4.57.2", - "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/fs-print": "4.64.0", + "@jsonjoy.com/fs-snapshot": "4.64.0", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, @@ -6584,9 +6113,9 @@ } }, "node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", - "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.64.0.tgz", + "integrity": "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6601,15 +6130,15 @@ } }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", - "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.64.0.tgz", + "integrity": "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-fsa": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2" + "@jsonjoy.com/fs-fsa": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0" }, "engines": { "node": ">=10.0" @@ -6623,13 +6152,14 @@ } }, "node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", - "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.64.0.tgz", + "integrity": "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.57.2" + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "glob-to-regex.js": "^1.0.1" }, "engines": { "node": ">=10.0" @@ -6643,13 +6173,13 @@ } }, "node_modules/@jsonjoy.com/fs-print": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", - "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.64.0.tgz", + "integrity": "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.64.0", "tree-dump": "^1.1.0" }, "engines": { @@ -6664,14 +6194,14 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", - "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.64.0.tgz", + "integrity": "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, @@ -7010,16 +6540,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/@mdx-js/mdx/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/@mdx-js/react": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", @@ -7039,9 +6559,9 @@ } }, "node_modules/@mediabunny/aac-encoder": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@mediabunny/aac-encoder/-/aac-encoder-1.47.0.tgz", - "integrity": "sha512-JNzgdJoHMFFnv5imi1+dmjZMudsJ1zNCUCEkKjBh90cqGhAFg8xu4V2gsyxE2i6oq/YpWx23P+OvoANLSMcDzA==", + "version": "1.50.8", + "resolved": "https://registry.npmjs.org/@mediabunny/aac-encoder/-/aac-encoder-1.50.8.tgz", + "integrity": "sha512-A5Se/LZd6RmYq/h36lBMSEsHvsyW8d0toR7FrAwpsFYbK+DVQYf90KiBT1Aw/mzLXx8/ypIOORJnd1sVZqOvJQ==", "license": "MPL-2.0", "funding": { "type": "individual", @@ -7052,9 +6572,9 @@ } }, "node_modules/@mediabunny/flac-encoder": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@mediabunny/flac-encoder/-/flac-encoder-1.47.0.tgz", - "integrity": "sha512-VpKmJO0xlYcFCRD6JvJlMbNQ6d/6YMHdO1gFIqWlZABHjSSL6BquNNEgWSCv5vdF8ELDBwIYBVZEslakIB/7GA==", + "version": "1.50.8", + "resolved": "https://registry.npmjs.org/@mediabunny/flac-encoder/-/flac-encoder-1.50.8.tgz", + "integrity": "sha512-4cfN03SbEoQaG+eBeYFUAb1R1ALaAHzf43dXFzQTr/oO5ueqzTgBoG3coKrIvBaAMU7Vv36mrBKLX8bvTppdAQ==", "license": "MPL-2.0", "funding": { "type": "individual", @@ -7065,9 +6585,9 @@ } }, "node_modules/@mediabunny/mp3-encoder": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.47.0.tgz", - "integrity": "sha512-JyzZyGeGRm2HVUQaGJ/VZT9OG+kG00mA8SLP/f3CO7+qWAmBncKc16WYXWHbZEo8Jn/ZGA6De32S5R2bTQbDqA==", + "version": "1.50.8", + "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.50.8.tgz", + "integrity": "sha512-eBT/H30tTu8AmZXqZ5RTpJ9VmwVlwednajuOrrWp0GS6cbGXsGMQ60Qvr3ZMIE0QDiadlEzb8/ozR+doP+MC1g==", "license": "MPL-2.0", "funding": { "type": "individual", @@ -7078,18 +6598,18 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", - "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz", + "integrity": "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==", "license": "MIT", "dependencies": { - "@chevrotain/types": "~11.1.1" + "@chevrotain/types": "~11.1.2" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -7140,21 +6660,34 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -7164,9 +6697,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -7185,23 +6718,6 @@ "node": ">=6.6.0" } }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -7246,11 +6762,12 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.0.tgz", + "integrity": "sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==", "license": "MIT", "dependencies": { + "debug": "^4.4.3", "ip-address": "^10.2.0" }, "engines": { @@ -7294,9 +6811,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -7309,6 +6826,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -7346,12 +6872,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -7407,17 +6927,34 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@module-federation/error-codes": { @@ -7474,27 +7011,21 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" } }, "node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -7539,9 +7070,9 @@ } }, "node_modules/@nuxt/kit": { - "version": "3.21.8", - "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.8.tgz", - "integrity": "sha512-kg63DUPY5AHPn+9XM7u8rYcdWHXjzwfUscgRDuiC5YUciQ+xdLRhdwXelYFxEAx2nxJHossliiQXbMm/Fleivw==", + "version": "3.21.9", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.9.tgz", + "integrity": "sha512-SJ3W7INBJLwnmFQPm2BACiqS25enc6QIm87aLQCcxo/TFtRnWanYtgDdwyHqUHgOAGmq9pYjgk83B2bpBKnDTw==", "license": "MIT", "optional": true, "dependencies": { @@ -7550,8 +7081,8 @@ "defu": "^6.1.7", "destr": "^2.0.5", "errx": "^0.1.0", - "exsolve": "^1.0.8", - "ignore": "^7.0.5", + "exsolve": "^1.1.0", + "ignore": "^7.0.6", "jiti": "^2.7.0", "klona": "^2.0.6", "knitwork": "^1.3.0", @@ -7561,8 +7092,8 @@ "pkg-types": "^2.3.1", "rc9": "^3.0.1", "scule": "^1.3.0", - "semver": "^7.8.0", - "tinyglobby": "^0.2.16", + "semver": "^7.8.5", + "tinyglobby": "^0.2.17", "ufo": "^1.6.4", "unctx": "^2.5.0", "untyped": "^2.0.0" @@ -7571,16 +7102,6 @@ "node": ">=18.12.0" } }, - "node_modules/@nuxt/kit/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@nuxt/kit/node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -7592,65 +7113,65 @@ } }, "node_modules/@otplib/core": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.4.0.tgz", - "integrity": "sha512-JqOGcvZQi2wIkEQo8f3/iAjstavpXy6gouIDMHygjNuH6Q0FjbHOiXMdcE94RwfgDNMABhzwUmvaPsxvgm9NYw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.4.1.tgz", + "integrity": "sha512-KIXgK1hNtWJEBMTastbe1bpmuais+3f+ATeO8TkMs2rNkfGO1FbQy8+/UWVEu3TR/iTJerU0idkPudaPmLP2BA==", "license": "MIT" }, "node_modules/@otplib/hotp": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.4.0.tgz", - "integrity": "sha512-MJjE0x06mn2ptymz5qZmQveb+vWFuaIftqE0b5/TZZqUOK7l97cV8lRTmid5BpAQMwJDNLW6RnYxGeCRiNdekw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.4.1.tgz", + "integrity": "sha512-g9q04SwpG5ZtMnVkUcgcoAlwCH4YLROZN1qhyBwgkBzqYYVSYhpP6gSGaxGHwePLt1c+e6NqDlgIZN+e1/XPuA==", "license": "MIT", "dependencies": { - "@otplib/core": "13.4.0", - "@otplib/uri": "13.4.0" + "@otplib/core": "13.4.1", + "@otplib/uri": "13.4.1" } }, "node_modules/@otplib/plugin-base32-scure": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.4.0.tgz", - "integrity": "sha512-/t9YWJmMbB8bF5z8mXrBZc2FXBe8B/3hG5FhWr9K8cFwFhyxScbPysmZe8s1UTzSA6N+s8Uv8aIfCtVXPNjJWw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.4.1.tgz", + "integrity": "sha512-Fs/r5qisC05SRhT6xWXaypB6PVC0vgWf6zztmi0J5RnQ09OJiPDWCJFH6cDm6ANsrdvB9di7X+Jb7L13BoEbUA==", "license": "MIT", "dependencies": { - "@otplib/core": "13.4.0", - "@scure/base": "^2.0.0" + "@otplib/core": "13.4.1", + "@scure/base": "^2.2.0" } }, "node_modules/@otplib/plugin-crypto-noble": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.4.0.tgz", - "integrity": "sha512-KrvE4m7Zv+TT1944HzgqFJWJpKb6AyoxDbvhPStmBqdMlv5Gekb80d66cuFRL08kkPgJ5gXUSb5SFpYeB+bACg==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.4.1.tgz", + "integrity": "sha512-PJfVW8/1hdS6CfxLheKPZSLTwDq4TijZbN4yRjxlv0ODdzmxpM+wGwWr1JXMdy0xJPxLziydQD5gdVqrR4/gAg==", "license": "MIT", "dependencies": { - "@noble/hashes": "^2.0.1", - "@otplib/core": "13.4.0" + "@noble/hashes": "^2.2.0", + "@otplib/core": "13.4.1" } }, "node_modules/@otplib/totp": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.4.0.tgz", - "integrity": "sha512-dK+vl0f0ekzf6mCENRI9AKS2NJUC7OjI3+X8e7QSnhQ2WM7I+i4PGpb3QxKi5hxjTtwVuoZwXR2CFtXdcRtNdQ==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.4.1.tgz", + "integrity": "sha512-QOkBVPrf6AM4qZaReZPSk9/I8ATVdZpIISJz115MqeVtcrbcr5llPZ0J7804tpnjnp1vCRkI5Qjd47HhgVteBQ==", "license": "MIT", "dependencies": { - "@otplib/core": "13.4.0", - "@otplib/hotp": "13.4.0", - "@otplib/uri": "13.4.0" + "@otplib/core": "13.4.1", + "@otplib/hotp": "13.4.1", + "@otplib/uri": "13.4.1" } }, "node_modules/@otplib/uri": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.4.0.tgz", - "integrity": "sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.4.1.tgz", + "integrity": "sha512-xaIm7bvICMhoB2rZIR5luiaMdssWR5nY5nXnR1fdezUgZuEO58D6zrGzLp7pQuBmlpmL0HagnscDQFoskp9yiA==", "license": "MIT", "dependencies": { - "@otplib/core": "13.4.0" + "@otplib/core": "13.4.1" } }, "node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.131.0.tgz", - "integrity": "sha512-t2xicr9pfzkSRYx5aPqZqlLaayIwJTqgQ81Jor31Xep2nGyL2Aq3d0K5wOfeR7VevaSdxaS9dzSQP9xDwn8fDg==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.140.0.tgz", + "integrity": "sha512-ZfjDZ422mo7eo3b3VltqNsV9kmv1qt/sPEAMSl64iOSwhVfd0eIZ9LB79Mbs1xYXJnk7WSROwzBCKDIiVxPTvQ==", "cpu": [ "arm" ], @@ -7664,9 +7185,9 @@ } }, "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.131.0.tgz", - "integrity": "sha512-nlGIod6gw75x1aEDgLS+srj+JRGY0HHm9MI9YgzE/B64l6d6+H3MSP9NOgp0+HTg8tp4vV9rVfgQGgd+TfVZcA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.140.0.tgz", + "integrity": "sha512-Ia8jSvikUX6Sf+Ht+KOCUF/k1HpR0VlmqIYymubmWDebOEGtsyliHDR6JxsZ4IX3/c/GbrB1uh09aVGQv/LQmQ==", "cpu": [ "arm64" ], @@ -7680,9 +7201,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.131.0.tgz", - "integrity": "sha512-jukuV6xe5RbQKFo7QD34NDCLDZp4PSOm8rmckhNdH/60ymG5zXbDzGBEyc+nTkuLQNama2aSGCt+CPfpjNTqyw==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.140.0.tgz", + "integrity": "sha512-G6VK0nK61pH0d0mBjUqSZbVxGqqO5uzeginLDQj+gOO6ObfJjXRwgkD/ol0w1INcnFeAb6YGGO7qc3ueGHaycQ==", "cpu": [ "arm64" ], @@ -7696,9 +7217,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.131.0.tgz", - "integrity": "sha512-g3JOo4khe9rslHm5WYaVDWb0HS/M1MLR3I9S8560MkKIcC96VQY00QjOlsuRyfSj/JDXj8i9T7ryPO2RidiXVg==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.140.0.tgz", + "integrity": "sha512-HazBOuZzd2pO1C2uMmp8Gv7mhzMHqKSKDS1OZfcLEvpIcgA+48J92HEtNanVHDIzRD9PRPCV6aS6fkZIWOVl8Q==", "cpu": [ "x64" ], @@ -7712,9 +7233,9 @@ } }, "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.131.0.tgz", - "integrity": "sha512-1hziITDTxjMePnX+dR9ocVT+EuZkQ8wm4FPAbmbEiKG+Phbo73J1ZnPAA6Y/aGsWF3McOFnQuZIktAFwalkfJQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.140.0.tgz", + "integrity": "sha512-9hSUU+HmTUyOe4JzMHxNGgLWNY7rrO+6ShicZwImNJacEAACDMIkuEQQkvXSL+WJN50jaNtLYJv8s4OcBdpyUQ==", "cpu": [ "x64" ], @@ -7728,9 +7249,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.131.0.tgz", - "integrity": "sha512-9uRxfXwyKG9+MwmGQBo2ncPNwZH5HTmCETFM2WiuDBNDCW4NC5ttSQkwCAMrTAWgwMzVBH1CP8pM0v7nebCWXQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.140.0.tgz", + "integrity": "sha512-RAEuQsYtS0KcDFqN0ABTjyyNlokS91JeuDuoW9tEbG0JTbRNXnpQUdbYc/16JoA6Z/2ALbNrE3KmxtqDiuIjCQ==", "cpu": [ "arm" ], @@ -7744,9 +7265,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.131.0.tgz", - "integrity": "sha512-mgbLvzRShXOLBdWGInf08Af4q+pfj1xD8hSgLClDZ9of/BXkB6+LIhTH7fihiDUipqB3yoSkKBWaZ3Ejlf5Yag==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.140.0.tgz", + "integrity": "sha512-c4CkHvPvqfojouredJ0w3e6+jiBq0SbFyhH61kr/zPb/7XsaYTNKQ54vmlSsopfdQbNDX40ZeK9Abs2Qet6wcw==", "cpu": [ "arm" ], @@ -7760,9 +7281,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.131.0.tgz", - "integrity": "sha512-OPT8++4aN6j2GJ8+3IZHS/byXoZP4aSBn+FoG6rgBJ2fKwPKXWF3MqrFMNW7NKHM28FLY579xYLxJSfgobEqPA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.140.0.tgz", + "integrity": "sha512-yrjmLj8ixPB25yqvPGr28meGjb+keed7m1GqqY/0uqkhZIoT4t9zmfwUgFEtC33C7dtE+UQ7TU0IaVxf97SWJg==", "cpu": [ "arm64" ], @@ -7776,9 +7297,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.131.0.tgz", - "integrity": "sha512-vtPiwmfVTAXzaxDKsOXG+LwgRAA7WEnaeHzhS5z0GE89gAK18KSXnly7Z6saXXq6L3dVMyK44uoTI03zKxrpmw==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.140.0.tgz", + "integrity": "sha512-ggGMQTN8Agwxp2WiLMpdY671dt0qTDJWiWlJeig3HnUwTnerRl0J2JdGVghWBeDcss2D9S2V2Js6dZHEiVabVA==", "cpu": [ "arm64" ], @@ -7792,9 +7313,9 @@ } }, "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.131.0.tgz", - "integrity": "sha512-8AW8L7w5cGHSdZPcyZX2yR0+GUODsT15rbRjfdD54rv6DMbtuEB19ysLOpKJlRGfH6UNYNpCHaU1uJWgTWf1/w==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.140.0.tgz", + "integrity": "sha512-IgTs8xYAFgAUGNmR65tIqjlJ8vKgrfXzC515e9goSdfMyKQV4aJpd2pUUudU4u51G64H0/DSEJEXKOraxm9ZCA==", "cpu": [ "ppc64" ], @@ -7808,9 +7329,9 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.131.0.tgz", - "integrity": "sha512-vvpjkjEOUsPcsYf8evE4MO3aGx9+3wodXEBOicGNnOwTuAik8eBONNkgSdhkGsAblQmfVHJyanRnpxglddTXIA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.140.0.tgz", + "integrity": "sha512-A1x+PMWZmSGaFVOx2YeNTFau8uD+QO14/vLP4GrcuvUPs3+nBkUOjy9Lus86ftHsDojjYMbvBelmKc3F7Rv08g==", "cpu": [ "riscv64" ], @@ -7824,9 +7345,9 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.131.0.tgz", - "integrity": "sha512-AqmcNC3fClXX+fxQ6VGEN1667xVFiRBkY0CZmDMSiaeFUsv1+UkBPYYi48IUKcA9/ivvoKNRzQl2I4//kT9F/w==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.140.0.tgz", + "integrity": "sha512-zBqpfRo2myWPrPo5xUjeZqlnPXPXsX8BcWtWff66/eGRQdbPjhzPgXa/F+AtxT2afUViPxbuDlwscMKzQ5tg+g==", "cpu": [ "riscv64" ], @@ -7840,9 +7361,9 @@ } }, "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.131.0.tgz", - "integrity": "sha512-7d3jOMKy7RSQCcDLIci+ySll2FgsOMl/GiRux4q2JNv0zg4EdhFISa9idvrdN/HEUIQQJNg6dmveUeJl2YErGA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.140.0.tgz", + "integrity": "sha512-2M1DPm/8w9I//YzFlFC9qXw+r2tJFh5CYwRlYTq2vUJQS7qoQftEDeCZ8EnN7KHtvSiXvYj8mZI5pR7DpXmcEw==", "cpu": [ "s390x" ], @@ -7856,9 +7377,9 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.131.0.tgz", - "integrity": "sha512-JHK/h95qVqVQ+ITER837kcTdwBDFpFaNnOTYGCP0zdUSX/mLKC7tXOoyrTb6vG7iRPwGlcgBil3v2IjYw1FqJA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.140.0.tgz", + "integrity": "sha512-8aRDbZ/U/jO8N7go1MO72jtbpb4uswV8d7vOkMvt/BPgZiyEYvl1VIWK4ESxZZhnJ4tqwVldgX7dNiP/eB1Jdg==", "cpu": [ "x64" ], @@ -7872,9 +7393,9 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.131.0.tgz", - "integrity": "sha512-b2BO82O8azXAyf7EUgOPKu145nWypbNyk07HbU09fkzhm9lEA5oPvaN/M8Nlo7tOErVTa2WOgS4QbOnxAPXdDQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.140.0.tgz", + "integrity": "sha512-xRqpeI8U2sQQS1W5BMWRyMTxtagkuLG2dEWruet5lFsWHTvBth11/TpSaJatHdqVVwHN0q3uuoS9zRsGinq8hg==", "cpu": [ "x64" ], @@ -7888,9 +7409,9 @@ } }, "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.131.0.tgz", - "integrity": "sha512-GHO9glZaX7LkX/OGfluEPf1yjg+ehiFbUdowbX6uNWOQhmwKWU4m4+nZ9FJkrHNKuxyI1KKertMdGjVKCApKWA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.140.0.tgz", + "integrity": "sha512-GbGRe26MqAKciFRvXeHNQJ6VAHYs9R4miP89sEAncysM3n+f4lnyLWgsa9kklJNpfnxdq2yRoNYHFqwBckVimw==", "cpu": [ "arm64" ], @@ -7904,27 +7425,45 @@ } }, "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.131.0.tgz", - "integrity": "sha512-3SkikPaEFoih1N83qLVEDLRLeY4nYsf6JT9SnWiMCQ5lGQdKup6bEuKCqkRiG9dD1IIaFeYz9RjlciPmYoFIWA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.140.0.tgz", + "integrity": "sha512-vFiC1hqys+hkX1GnQkIoiTQJNiUm43Z0lO35ETKXTw0YtpW7+cN58YRRXFAQQ+TgpkIi3lrhcxdlnqz+Oi3ptQ==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.131.0.tgz", - "integrity": "sha512-Os5bEhryeA2jkH+ZrnZyAC1EP5gs+X4YB1Fjqml7UPD5kU7ecsK1MPEVMfCrdt/GDNpDbavYXiOXOdyJ5b3OPw==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.140.0.tgz", + "integrity": "sha512-fGSQldwEYKhM+H8uLt76Op8hh5+FYaR6lvvQ1Txw3Mhn86DyQXLcI0fi1EkFlTK7F+46OCk/j0AJMzZQm6g5Xg==", "cpu": [ "arm64" ], @@ -7938,9 +7477,9 @@ } }, "node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.131.0.tgz", - "integrity": "sha512-m+jNz9EuF0NXoiptc6B9h5yompZQVW/a5MJeOu5zojfH5yWk82tvF2ccrHkfhgtrS9h9DD5l1Qv8dWlfY7Nz8g==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.140.0.tgz", + "integrity": "sha512-sDS2Bai+g3ZWYwfZqmosiSuFDBcVnZ3Ta6pszzsiJoLMqsJEWKcxXXbGa7b7yXr++W2lQNPb3ZRJ8czseqL7RA==", "cpu": [ "ia32" ], @@ -7954,9 +7493,9 @@ } }, "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.131.0.tgz", - "integrity": "sha512-o14Hk8dAyiEUMFEWEgmAwFZvBt1RzAYLM3xeQ+5315JXgVYhoemivgYcbYVRbsFkS71ShMGlAFE0kPnr460rww==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.140.0.tgz", + "integrity": "sha512-kHbE1zWyb5OQgJA6/5P4WjiuB01sYdQwtZnSSyE58FQEXDAMnyeeq4vj7KgN75i5SlBzOs8A5MrtlD3gOlDKqQ==", "cpu": [ "x64" ], @@ -7970,9 +7509,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.131.0.tgz", - "integrity": "sha512-PgnWDfV0h+b16XNKbXU7Daib/BFSt/J2mEzfYIBu6JB/wNdlU+kVYXCkGA1A9fWkTbOgbjh4e6NhPeQOYvFhEA==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.140.0.tgz", + "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -8020,138 +7559,148 @@ } }, "node_modules/@peculiar/asn1-cms": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", - "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.8.0.tgz", + "integrity": "sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "@peculiar/asn1-x509-attr": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-csr": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", - "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.8.0.tgz", + "integrity": "sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-ecc": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", - "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.8.0.tgz", + "integrity": "sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pfx": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", - "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.8.0.tgz", + "integrity": "sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.1", - "@peculiar/asn1-pkcs8": "^2.6.1", - "@peculiar/asn1-rsa": "^2.6.1", - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-rsa": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", - "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.8.0.tgz", + "integrity": "sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", - "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.8.0.tgz", + "integrity": "sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.1", - "@peculiar/asn1-pfx": "^2.6.1", - "@peculiar/asn1-pkcs8": "^2.6.1", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "@peculiar/asn1-x509-attr": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pfx": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-rsa": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", - "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.8.0.tgz", + "integrity": "sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", - "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", "dev": true, "license": "MIT", "dependencies": { - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", - "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.8.0.tgz", + "integrity": "sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", - "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.8.0.tgz", + "integrity": "sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "dev": true, "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.1", - "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, @@ -8184,6 +7733,16 @@ "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/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -8215,9 +7774,9 @@ "license": "ISC" }, "node_modules/@pnpm/npm-conf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", - "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", "dev": true, "license": "MIT", "dependencies": { @@ -8287,15 +7846,15 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, "node_modules/@puppeteer/browsers": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", - "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", "license": "Apache-2.0", "dependencies": { "debug": "^4.4.3", @@ -8313,33 +7872,58 @@ "node": ">=18" } }, - "node_modules/@puppeteer/browsers/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" + "node_modules/@puppeteer/browsers/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" }, "peerDependenciesMeta": { - "supports-color": { + "react-native-b4a": { "optional": true } } }, - "node_modules/@puppeteer/browsers/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==", + "node_modules/@puppeteer/browsers/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==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/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/@puppeteer/browsers/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/@puppeteer/browsers/node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", "license": "MIT", "dependencies": { "pump": "^3.0.0", @@ -8351,9 +7935,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", "license": "MIT", "dependencies": { "b4a": "^1.6.4", @@ -8362,6 +7946,50 @@ "streamx": "^2.15.0" } }, + "node_modules/@puppeteer/browsers/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==", + "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/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "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/@puppeteer/browsers/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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@quansync/fs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", @@ -8391,21 +8019,21 @@ "license": "MIT" }, "node_modules/@remotion/bundler": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/bundler/-/bundler-4.0.479.tgz", - "integrity": "sha512-kP+r+hRQMHkU6f1q6Mav9wp6DdB06Sc7IjRGpVMFF2Qyvy1VkRDNvbk9/J8a16hhf08o0oDFoJ/l4IZaYu2sFw==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/bundler/-/bundler-4.0.496.tgz", + "integrity": "sha512-xbiQVuOTLGKZkwN2E+1/2zkf+Ei2dG1f2dj9A0htRygEvDDNm9n5OGw75StKpfkIiWY0NU5/Dr+IJeqlJIkVCQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@remotion/media-parser": "4.0.479", - "@remotion/studio": "4.0.479", - "@remotion/studio-shared": "4.0.479", - "@remotion/timeline-utils": "4.0.479", + "@remotion/media-parser": "4.0.496", + "@remotion/studio": "4.0.496", + "@remotion/studio-shared": "4.0.496", + "@remotion/timeline-utils": "4.0.496", "@rspack/core": "1.7.11", "@rspack/plugin-react-refresh": "1.6.1", "css-loader": "7.1.4", "esbuild": "0.28.1", "react-refresh": "0.18.0", - "remotion": "4.0.479", + "remotion": "4.0.496", "style-loader": "4.0.0", "webpack": "5.105.0" }, @@ -8498,13 +8126,13 @@ } }, "node_modules/@remotion/canvas-capture": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/canvas-capture/-/canvas-capture-4.0.479.tgz", - "integrity": "sha512-3LEHY5tdtAa4ATyUeV7/02dIIR89d7P3uBN8ova8OH856qFyc8qFt3sMT2qThf7XNFtxPeUuFjPZ0mUjQaxe4A==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/canvas-capture/-/canvas-capture-4.0.496.tgz", + "integrity": "sha512-XBKDD9XjR58fsuWgd8GuIzcVSEjQldDfzgneKxiqbCqIJBl+yQazS18LWabOefX8Ry6jXSOhEFm6vJZ+bv/+cQ==", "license": "Remotion License", "dependencies": { - "mediabunny": "1.47.0", - "remotion": "4.0.479" + "mediabunny": "1.50.8", + "remotion": "4.0.496" }, "peerDependencies": { "react": ">=16.8.0", @@ -8512,22 +8140,22 @@ } }, "node_modules/@remotion/cli": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/cli/-/cli-4.0.479.tgz", - "integrity": "sha512-82fn466M+4oJf8rvb8rdwfjvzHTehUiRfeF8XA2O5BLfa/ju6NyjoYjvRCwH3AIrSxvKjPxYnkmd9uoXid+SCA==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/cli/-/cli-4.0.496.tgz", + "integrity": "sha512-mecUm24TDJ+IUZko17Kf9Q8kR5Zg+lkg44L6pf5KBeSbmHNpf21kILhoIOE3E2sPk3652UJFoxVlTmSrOZz/FA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@remotion/bundler": "4.0.479", - "@remotion/media-utils": "4.0.479", - "@remotion/player": "4.0.479", - "@remotion/renderer": "4.0.479", - "@remotion/studio": "4.0.479", - "@remotion/studio-server": "4.0.479", - "@remotion/studio-shared": "4.0.479", + "@remotion/bundler": "4.0.496", + "@remotion/media-utils": "4.0.496", + "@remotion/player": "4.0.496", + "@remotion/renderer": "4.0.496", + "@remotion/studio": "4.0.496", + "@remotion/studio-server": "4.0.496", + "@remotion/studio-shared": "4.0.496", "dotenv": "17.3.1", "minimist": "1.2.6", "prompts": "2.4.2", - "remotion": "4.0.479" + "remotion": "4.0.496" }, "bin": { "remotion": "remotion-cli.js", @@ -8551,16 +8179,10 @@ "url": "https://dotenvx.com" } }, - "node_modules/@remotion/cli/node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "license": "MIT" - }, "node_modules/@remotion/compositor-darwin-arm64": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.479.tgz", - "integrity": "sha512-Mk0hLkhMkSnpCQtU97osgsUCoFmDwk6pKl/oPYO47uB6m2WW60Id8jJN/Bge4wlvbfOFI7Wjueu6zYD3ziGk7g==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.496.tgz", + "integrity": "sha512-cqtCPf5Bf1Fb+Ef8MZ06jm+IPuB0oqft3vNW8ms2Vs8Ox0ujRV28SyQR1J4kdkNS3YegPCc0mocERl15U8X3Cw==", "cpu": [ "arm64" ], @@ -8570,9 +8192,9 @@ ] }, "node_modules/@remotion/compositor-darwin-x64": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.479.tgz", - "integrity": "sha512-ISFBEKuNn21hLrXlCeBc7ye4Clb4OQO15msnbp/0vp50VxEXOJEAnIAkQ72Mp1j34g4cvId0tJucuJ8QFERL6g==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.496.tgz", + "integrity": "sha512-A+56bCMtBCFT0yWo4036hKdsZd1GObvY6uVesGjjz9deCFlj1VWi8KqJtWOKdovzpOSlpohDMbkZ0NU6a3QUyg==", "cpu": [ "x64" ], @@ -8582,9 +8204,9 @@ ] }, "node_modules/@remotion/compositor-linux-arm64-gnu": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.479.tgz", - "integrity": "sha512-gmnrYBE6pOkPcaI9GiDf1JFxBy/jDNw3RTBCC2AvgPqvl15Yn8h1HcPrm2dMFmlYt9iSjOZeG1V4bTmBqQ8plQ==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.496.tgz", + "integrity": "sha512-UUYptpBS+jg3amuJxE/XB5nFwPyrG+mX2GL2zWwa7YvrUCrodO9j9uXf/LULRh5G2NQIFs71XCCmcXdGJ3CACA==", "cpu": [ "arm64" ], @@ -8594,9 +8216,9 @@ ] }, "node_modules/@remotion/compositor-linux-arm64-musl": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.479.tgz", - "integrity": "sha512-YYXiNdoMgFfWdKWoTBqzLAKBSmn684x4LXEuMCIvyT/N8T/B7p8afIse9jx6zn7Pw2Gxudmtur/S83qP6Vz2gw==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.496.tgz", + "integrity": "sha512-xtoYUwJYmk6Wc/WkowpisNi/ENuRD68u+SVmSR95duSo8092t6OM6NdxINoE3SNyvHaT3nUuuAsHEXI2f8VF7Q==", "cpu": [ "arm64" ], @@ -8606,9 +8228,9 @@ ] }, "node_modules/@remotion/compositor-linux-x64-gnu": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.479.tgz", - "integrity": "sha512-+s9VdDjuDG+JIVIDR6i+wZ8oP+BowJK9v/l4eFClPK+HG4am/TzlHK0PouITutF0skfbqy1OEol4pbrKnfZtKg==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.496.tgz", + "integrity": "sha512-GO2U8QAUFXklnhGkSTS+o69S6wZeW6FflCEBHQ7AJjRsHHI5bZv2my1NaJm1Jb9LxWKdXDARLXGpyceJShC2lg==", "cpu": [ "x64" ], @@ -8618,9 +8240,9 @@ ] }, "node_modules/@remotion/compositor-linux-x64-musl": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.479.tgz", - "integrity": "sha512-4ADB/rzSGB7ia0ppgLQN3/JgPcgIyPef47v3QL4zOhuOwkWm5OAtHypB7yb8iI9UV73Zc/IfyqwLe3FVcrNaEA==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.496.tgz", + "integrity": "sha512-89Bieal/oAMIowlClYsZL9mlBf0/ho6OWvWgTQ04WOG/nvKE1A4q/N9D/LRudlRNOXg/mai8F2GwSTBTHU96Tg==", "cpu": [ "x64" ], @@ -8630,9 +8252,9 @@ ] }, "node_modules/@remotion/compositor-win32-x64-msvc": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.479.tgz", - "integrity": "sha512-UQyhORMiRUr3qam4gACYSVq4Qfimq9dlnzbzyblZ3gxC0cTK2o2d6q+o0Dl023xOAxO34UIVzUWkaeKZ0xAIBw==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.496.tgz", + "integrity": "sha512-TTqOB8slQAkY/U9kq4gE/Qt/gvyJUPadm2y3Dx5UQ6t8o8axbGZBjnQn7kJTMR9zkzltnJHcng/+bdAsrEjn/Q==", "cpu": [ "x64" ], @@ -8642,25 +8264,25 @@ ] }, "node_modules/@remotion/licensing": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/licensing/-/licensing-4.0.479.tgz", - "integrity": "sha512-VZROKokeU4Ti49/nXBiQHW1gXzkgRlUZLkapHsp02DQw+KsWqDXaAK/B1bL4stHa5WA0e7puXZQt7t+7cbPBug==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/licensing/-/licensing-4.0.496.tgz", + "integrity": "sha512-l5mA4I8oTiFbEcLojmKyl88/MgJchJtwQRDecKVDFkH2bRhEz6YVStLW9rC6mV3Lk+QT8BtAH2zuAQBeLHJ/4w==", "license": "MIT" }, "node_modules/@remotion/media-parser": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.479.tgz", - "integrity": "sha512-y4u90CEng1YaUlg1ukulVH0APe1d7qOBXumQ2ShMwKnK7Tf3gIHVnFGNdynXpgiJzxF3NLDvFXB5vQKQ+8DSIg==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.496.tgz", + "integrity": "sha512-Dtp2qoS6JhTdJGNC5r7CDgOKmIesGVG//aM9ZgQJnycJD+Guo6FWzGxdgJZk2ITuSj+9gN7qQp7Y6g1GVEmY+Q==", "license": "Remotion License https://remotion.dev/license" }, "node_modules/@remotion/media-utils": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.479.tgz", - "integrity": "sha512-GekawJ3p7cCclKzzmmY4vj8EstKf8JXuRE24zDzEbRZiGOyCpyrqcxTDQoUj9MsWcdlLZtNXlLZJffLJ+BRjiw==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.496.tgz", + "integrity": "sha512-Av0AaTw42wG5Tfju6cfMYoDpQKSdnnTIeewiXL6EL2eM3vE+XhTwOXP1ta9K+YSCtP6fMNvkbESs1ohqzmqMnw==", "license": "MIT", "dependencies": { - "mediabunny": "1.47.0", - "remotion": "4.0.479" + "mediabunny": "1.50.8", + "remotion": "4.0.496" }, "peerDependencies": { "react": ">=16.8.0", @@ -8668,12 +8290,12 @@ } }, "node_modules/@remotion/player": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.479.tgz", - "integrity": "sha512-oluW+lTOhNhDWpR24N8JN7ZnghTExGqPBg/HAG8UetEpizCYCTE+MSyf4sggf68jjJOte/f/pF4zMduQRtd0Gw==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.496.tgz", + "integrity": "sha512-eQ1f384arsdRrfnsp9GZMdP43yk9zj224ZQaBBwscsl4lK8BhjfoOXOHne03MI4m9tw7XgmQC1bLZtTgnfW5rA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "remotion": "4.0.479" + "remotion": "4.0.496" }, "peerDependencies": { "react": ">=16.8.0", @@ -8681,26 +8303,26 @@ } }, "node_modules/@remotion/renderer": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/renderer/-/renderer-4.0.479.tgz", - "integrity": "sha512-t7rNPESRpqwhKPBx5F3XXQdQwzh7lvs4oR3ApDRscyCOAMYB4xg7zv1UfK+2zvVETbdZ9AJDvQ/nOvkO2x7WnA==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/renderer/-/renderer-4.0.496.tgz", + "integrity": "sha512-jSrTASFGeU1wDgkBB86TH0CPbHFVntHDZ1kve1C0G5IntnxwwndD4AjcABun9E9U5fsPiAimTyG+jwx0F9+uzQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@remotion/licensing": "4.0.479", - "@remotion/streaming": "4.0.479", + "@remotion/licensing": "4.0.496", + "@remotion/streaming": "4.0.496", "execa": "5.1.1", - "remotion": "4.0.479", + "remotion": "4.0.496", "source-map": "0.8.0-beta.0", "ws": "8.21.0" }, "optionalDependencies": { - "@remotion/compositor-darwin-arm64": "4.0.479", - "@remotion/compositor-darwin-x64": "4.0.479", - "@remotion/compositor-linux-arm64-gnu": "4.0.479", - "@remotion/compositor-linux-arm64-musl": "4.0.479", - "@remotion/compositor-linux-x64-gnu": "4.0.479", - "@remotion/compositor-linux-x64-musl": "4.0.479", - "@remotion/compositor-win32-x64-msvc": "4.0.479" + "@remotion/compositor-darwin-arm64": "4.0.496", + "@remotion/compositor-darwin-x64": "4.0.496", + "@remotion/compositor-linux-arm64-gnu": "4.0.496", + "@remotion/compositor-linux-arm64-musl": "4.0.496", + "@remotion/compositor-linux-x64-gnu": "4.0.496", + "@remotion/compositor-linux-x64-musl": "4.0.496", + "@remotion/compositor-win32-x64-msvc": "4.0.496" }, "peerDependencies": { "react": ">=16.8.0", @@ -8747,30 +8369,30 @@ } }, "node_modules/@remotion/streaming": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/streaming/-/streaming-4.0.479.tgz", - "integrity": "sha512-wsCKAENXwuRGktWsTSVwzi/sKhV4vNUFco+Fh9gbVTArlNEjQqpAwV/fxqMEVaJVT9ROaj1DUiFdSw9HlFDL5g==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/streaming/-/streaming-4.0.496.tgz", + "integrity": "sha512-drMlOZZRJ/iVvU+Kr/4T5B2UA7ctZFvUXbSuf4+p1kWN9pz6Th/sEYQfwx/8MEjzkWiKb23vuPLc4lTEFBBJIA==", "license": "MIT" }, "node_modules/@remotion/studio": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/studio/-/studio-4.0.479.tgz", - "integrity": "sha512-//clTt1xGxBLIn+ocA17nn1+Vd4Mgd5ZuvlNSGub+TQtTZ+H7cfZed5M18RcL4hb1ulm/YYAQKXGQmluYAoT4A==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/studio/-/studio-4.0.496.tgz", + "integrity": "sha512-PeTWAzULiOqdQJB/4UGPeDNiZI+ZbvKtVNTG3787ad45xD+f7r9SZz6gQ9DJ1DRrxR6M3zke6xLKI1EFFSYBZg==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.31", - "@remotion/canvas-capture": "4.0.479", - "@remotion/media-utils": "4.0.479", - "@remotion/player": "4.0.479", - "@remotion/renderer": "4.0.479", - "@remotion/studio-shared": "4.0.479", - "@remotion/timeline-utils": "4.0.479", - "@remotion/web-renderer": "4.0.479", - "@remotion/zod-types": "4.0.479", - "mediabunny": "1.47.0", + "@remotion/canvas-capture": "4.0.496", + "@remotion/media-utils": "4.0.496", + "@remotion/player": "4.0.496", + "@remotion/renderer": "4.0.496", + "@remotion/studio-shared": "4.0.496", + "@remotion/timeline-utils": "4.0.496", + "@remotion/web-renderer": "4.0.496", + "@remotion/zod-types": "4.0.496", + "mediabunny": "1.50.8", "memfs": "3.4.3", "open": "8.4.2", - "remotion": "4.0.479", + "remotion": "4.0.496", "semver": "7.5.3", "zod": "4.3.6" }, @@ -8780,21 +8402,24 @@ } }, "node_modules/@remotion/studio-server": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/studio-server/-/studio-server-4.0.479.tgz", - "integrity": "sha512-0F0nFS+sStNSMrkPY7TikOTkedl1MCJvslJIWwbk0DQj0MxA9FjPtZW/hoVpn29uebt/XdcHLzl3caFwW/h27A==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/studio-server/-/studio-server-4.0.496.tgz", + "integrity": "sha512-haMfZ1COAuvDwVCwrlH+zfyueVhIC4H5pDuh+7JRfCNhvdczHkxzOLmpS3K90qKgmRykhBo7SFGqerKp3cRmxQ==", "license": "MIT", "dependencies": { "@babel/parser": "7.24.1", "@babel/types": "7.24.0", - "@remotion/bundler": "4.0.479", - "@remotion/renderer": "4.0.479", - "@remotion/studio-shared": "4.0.479", + "@remotion/bundler": "4.0.496", + "@remotion/renderer": "4.0.496", + "@remotion/studio-shared": "4.0.496", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "kiwi-schema": "0.5.0", "memfs": "3.4.3", "open": "8.4.2", "prettier": "3.8.1", "recast": "0.23.11", - "remotion": "4.0.479", + "remotion": "4.0.496", "semver": "7.5.3" } }, @@ -8836,18 +8461,6 @@ "node": ">=10" } }, - "node_modules/@remotion/studio-server/node_modules/memfs": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", - "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", - "license": "Unlicense", - "dependencies": { - "fs-monkey": "1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/@remotion/studio-server/node_modules/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", @@ -8870,24 +8483,12 @@ "license": "ISC" }, "node_modules/@remotion/studio-shared": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/studio-shared/-/studio-shared-4.0.479.tgz", - "integrity": "sha512-L2pGZTh/wlCM+97JqiGm5ga+6tO9ssDMTLXP9QoPc2v8cUu42OMQENvm3eom36E92cFeIZe4nqkxMKUpYx5dgg==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/studio-shared/-/studio-shared-4.0.496.tgz", + "integrity": "sha512-30RuyXIaPb1VXtSqlKH4gJwtjIDy8+LzuRt4L0utafZ9jvRhFuvf6Mn2vFuJYlKs4GOlu0q2qgru90ydhSWi1w==", "license": "MIT", "dependencies": { - "remotion": "4.0.479" - } - }, - "node_modules/@remotion/studio/node_modules/@remotion/zod-types": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/zod-types/-/zod-types-4.0.479.tgz", - "integrity": "sha512-bhyVjjTR3odo/NQ6BQjmLI3xC7La8Vz8t8A3wlLz+u1MpS/qfAEP6wmYvhM+bHo3Ukipsb4oy9a2gQRtPGb9aA==", - "license": "MIT", - "dependencies": { - "remotion": "4.0.479" - }, - "peerDependencies": { - "zod": "4.3.6" + "remotion": "4.0.496" } }, "node_modules/@remotion/studio/node_modules/lru-cache": { @@ -8902,18 +8503,6 @@ "node": ">=10" } }, - "node_modules/@remotion/studio/node_modules/memfs": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", - "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", - "license": "Unlicense", - "dependencies": { - "fs-monkey": "1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/@remotion/studio/node_modules/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", @@ -8945,36 +8534,45 @@ } }, "node_modules/@remotion/timeline-utils": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/timeline-utils/-/timeline-utils-4.0.479.tgz", - "integrity": "sha512-JyvO1NocSGctgemlNh7ZJKbdodTiuUZmL/bFQ9U6pzw6o4+1q20sd2+Kqua+lJKbIcDigXjqUIBttYiM6ZO/1g==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/timeline-utils/-/timeline-utils-4.0.496.tgz", + "integrity": "sha512-PKN/oBAaeR8QWW0dFB5A6eiWcS19QJiLoc8s2ns/KlnVCkBPFv2gB3hPcqwKL0wtFP9ydvxl8l3wUFx9BFZUKg==", "license": "MIT", "dependencies": { - "mediabunny": "1.47.0" + "mediabunny": "1.50.8" } }, "node_modules/@remotion/web-renderer": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/@remotion/web-renderer/-/web-renderer-4.0.479.tgz", - "integrity": "sha512-JLICwVsWoyJnc/2Dme5V7eY1v1RqhjeVLWxv9orIdvOIJD2byq473VmZh7ldwzZNmzx6zJWxWrarhraX7nIW2g==", - "license": "UNLICENSED", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/web-renderer/-/web-renderer-4.0.496.tgz", + "integrity": "sha512-kpQQB2zPPh7h2AY9uw0lEIRBFpCrjxsXoFwq0tY6EKt1SgRfB7cxw5T5TrlLcatv61MD2oSDfTha/iLvmfyuVw==", + "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@mediabunny/aac-encoder": "1.47.0", - "@mediabunny/flac-encoder": "1.47.0", - "@mediabunny/mp3-encoder": "1.47.0", - "@remotion/licensing": "4.0.479", - "mediabunny": "1.47.0", - "remotion": "4.0.479" + "@mediabunny/aac-encoder": "1.50.8", + "@mediabunny/flac-encoder": "1.50.8", + "@mediabunny/mp3-encoder": "1.50.8", + "@remotion/licensing": "4.0.496", + "mediabunny": "1.50.8", + "remotion": "4.0.496" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, + "node_modules/@remotion/zod-types": { + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/@remotion/zod-types/-/zod-types-4.0.496.tgz", + "integrity": "sha512-OGvqDqhqCVLArbtAT8m+fb2pAfqPnr993m4h4YbS7HU8QzSsT5TF2Z8bJUEI4dmKJvN2IDkJPfC4AaiB+i2dMA==", + "license": "MIT", + "dependencies": { + "remotion": "4.0.496" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -8988,9 +8586,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -9004,9 +8602,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -9020,9 +8618,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -9036,9 +8634,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -9052,9 +8650,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -9068,9 +8666,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -9084,9 +8682,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -9100,9 +8698,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -9116,9 +8714,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -9132,9 +8730,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -9148,9 +8746,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -9164,27 +8762,66 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -9198,9 +8835,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -9328,18 +8965,6 @@ "@napi-rs/wasm-runtime": "1.0.7" } }, - "node_modules/@rspack/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" - } - }, "node_modules/@rspack/binding-win32-arm64-msvc": { "version": "1.7.11", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.11.tgz", @@ -9450,9 +9075,9 @@ } }, "node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz", + "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==", "license": "MIT", "engines": { "node": ">=v14.0.0", @@ -9460,22 +9085,22 @@ } }, "node_modules/@scure/base": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", - "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.2.0.tgz", + "integrity": "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==", "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@shikijs/core": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz", - "integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz", + "integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==", "license": "MIT", "dependencies": { - "@shikijs/primitive": "4.2.0", - "@shikijs/types": "4.2.0", + "@shikijs/primitive": "4.3.1", + "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" @@ -9485,12 +9110,12 @@ } }, "node_modules/@shikijs/engine-javascript": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz", - "integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz", + "integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0", + "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" }, @@ -9499,12 +9124,12 @@ } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz", - "integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz", + "integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0", + "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { @@ -9512,21 +9137,21 @@ } }, "node_modules/@shikijs/langs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz", - "integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.3.1.tgz", + "integrity": "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0" + "@shikijs/types": "4.3.1" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/magic-move": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/magic-move/-/magic-move-4.2.0.tgz", - "integrity": "sha512-KJBoKtpl04+ee6umjgt/qBveQPkCt+cMfaWJWbrRqqBtMCY8q0kufMPSjzJwLhNuSs2c0/D3BteOCOFAwg28rg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/magic-move/-/magic-move-4.3.1.tgz", + "integrity": "sha512-MZ2Zvb8NuCJWYYJbjCeygeMh2qReF40beuWUTGuBpNZPKQA1j+rJaQ+dctZgt6arTB80pbIVsgjcadpALASvxA==", "license": "MIT", "dependencies": { "diff-match-patch-es": "^1.0.1", @@ -9561,13 +9186,13 @@ } }, "node_modules/@shikijs/markdown-it": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-4.2.0.tgz", - "integrity": "sha512-PpjZsE1JmY/2x6XFklfRJsFH6hjY0VY18Ny5Tm1/rZPpzBjuzyzOzD+fBtaud0NyO3PdCfcNPQQlbgHUz6m45w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-4.3.1.tgz", + "integrity": "sha512-dceX0rAylb4Rivp2dzbLEtxgG/DcW+dsiQJEqes0ohKiR1zlaWh4eQE43eI0TkzNdno/tDA6QJzYE5/CU3mTIQ==", "license": "MIT", "dependencies": { "markdown-it": "^14.2.0", - "shiki": "4.2.0" + "shiki": "4.3.1" }, "engines": { "node": ">=20" @@ -9582,13 +9207,13 @@ } }, "node_modules/@shikijs/monaco": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/monaco/-/monaco-4.2.0.tgz", - "integrity": "sha512-NBAY81HWX6wNYsbedqzkg3c75/XiwncUthARZNMp2Ac3656HjCfyfAdiRujdxq93LFTZJ5545yIyBz8hKMKZpw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/monaco/-/monaco-4.3.1.tgz", + "integrity": "sha512-A8lB7DKVMWmT+EgzBQkw7pqMsc1NUogRM+CEyv3ldl0CcTctPd0oz7Lcb0d1iZmWYO3WJdpMUuISS4GAexHvhA==", "license": "MIT", "dependencies": { - "@shikijs/core": "4.2.0", - "@shikijs/types": "4.2.0", + "@shikijs/core": "4.3.1", + "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { @@ -9596,12 +9221,12 @@ } }, "node_modules/@shikijs/primitive": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz", - "integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz", + "integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0", + "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, @@ -9610,26 +9235,26 @@ } }, "node_modules/@shikijs/themes": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz", - "integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz", + "integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0" + "@shikijs/types": "4.3.1" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/twoslash": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/twoslash/-/twoslash-4.2.0.tgz", - "integrity": "sha512-PG/F0tMyt4zAvHVBL7Ehtk/ZpI2Rq3PwaXRYJaOO41eIK/iV9GOO/20jZhQkScOdcP6aFwHC2/x/GxmeR4tMlA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/twoslash/-/twoslash-4.3.1.tgz", + "integrity": "sha512-xK8inH/gK++1V4rTxrwCwjvaNwkkJ7oDjOIpdqONVxIpAFnVC3gzqjH5KiXGTelUcxpUJ3PtOKWct1YQ0kAloA==", "license": "MIT", "dependencies": { - "@shikijs/core": "4.2.0", - "@shikijs/types": "4.2.0", - "twoslash": "^0.3.8" + "@shikijs/core": "4.3.1", + "@shikijs/types": "4.3.1", + "twoslash": "^0.3.9" }, "engines": { "node": ">=20" @@ -9639,9 +9264,9 @@ } }, "node_modules/@shikijs/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz", - "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz", + "integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -9652,12 +9277,12 @@ } }, "node_modules/@shikijs/vitepress-twoslash": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/vitepress-twoslash/-/vitepress-twoslash-4.2.0.tgz", - "integrity": "sha512-xji8w8GLho9XuBi+1chJg3K4jRiM9dSq21tL44HrxP2mkRqTadUOcCT+cW2fbjcLE6Zq6AkRizDWXD7yFjyVTg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/vitepress-twoslash/-/vitepress-twoslash-4.3.1.tgz", + "integrity": "sha512-IZ+LTUPaXjQAUkOcTKh/QBOMLZ/y8uJksqyDRbeUwBxQAFhAD3r9NWcNpMU0rNMeOHaSj5Da6P1Cswgnpg8gmQ==", "license": "MIT", "dependencies": { - "@shikijs/twoslash": "4.2.0", + "@shikijs/twoslash": "4.3.1", "floating-vue": "^5.2.2", "lz-string": "^1.5.0", "magic-string": "^0.30.21", @@ -9666,10 +9291,10 @@ "mdast-util-gfm": "^3.1.0", "mdast-util-to-hast": "^13.2.1", "ohash": "^2.0.11", - "shiki": "4.2.0", - "twoslash": "^0.3.8", - "twoslash-vue": "^0.3.8", - "vue": "^3.5.35" + "shiki": "4.3.1", + "twoslash": "^0.3.9", + "twoslash-vue": "^0.3.9", + "vue": "^3.5.38" }, "engines": { "node": ">=20" @@ -9706,9 +9331,9 @@ "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "dev": true, "license": "MIT" }, @@ -9726,29 +9351,30 @@ } }, "node_modules/@slidev/cli": { - "version": "52.16.0", - "resolved": "https://registry.npmjs.org/@slidev/cli/-/cli-52.16.0.tgz", - "integrity": "sha512-PWhNTNFhprVXpcFmpGXIayhTkItG/o+zdn5jmhtSy49z9ElhuUilCJHCExafSU43GOtNbFqdNo3oRsSIJs6P6A==", + "version": "52.18.0", + "resolved": "https://registry.npmjs.org/@slidev/cli/-/cli-52.18.0.tgz", + "integrity": "sha512-1Q2oz9tUpZfJ0sAL+Rg1xMUyylLRRFuMYffRLONLrG9Uftz5dp7cmHtSH5m2WO6ndsAz0f5I7THvBmjdbqVsmA==", "license": "MIT", "dependencies": { - "@antfu/ni": "^30.1.0", + "@antfu/ni": "^30.2.0", "@antfu/utils": "^9.3.0", "@comark/markdown-it": "^0.3.4", - "@iconify-json/carbon": "^1.2.22", + "@iconify-json/carbon": "^1.2.24", "@iconify-json/ph": "^1.2.2", "@iconify-json/svg-spinners": "^1.2.4", "@lillallol/outline-pdf": "^4.0.0", - "@shikijs/magic-move": "^4.2.0", - "@shikijs/markdown-it": "^4.2.0", - "@shikijs/twoslash": "^4.2.0", - "@shikijs/vitepress-twoslash": "^4.2.0", - "@slidev/client": "52.16.0", - "@slidev/parser": "52.16.0", - "@slidev/types": "52.16.0", - "@unocss/extractor-mdc": "^66.7.0", - "@unocss/reset": "^66.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "@shikijs/magic-move": "^4.3.1", + "@shikijs/markdown-it": "^4.3.1", + "@shikijs/twoslash": "^4.3.1", + "@shikijs/vitepress-twoslash": "^4.3.1", + "@slidev/client": "52.18.0", + "@slidev/parser": "52.18.0", + "@slidev/types": "52.18.0", + "@unocss/extractor-mdc": "^66.7.5", + "@unocss/reset": "^66.7.5", "@vitejs/plugin-vue": "^6.0.7", - "@vitejs/plugin-vue-jsx": "^5.1.5", + "@vitejs/plugin-vue-jsx": "^5.1.6", "ansis": "^4.3.1", "chokidar": "^5.0.0", "cli-progress": "^3.12.0", @@ -9765,15 +9391,16 @@ "lz-string": "^1.5.0", "magic-string": "^0.30.21", "magic-string-stack": "^1.1.0", - "markdown-exit": "^1.0.0-beta.9", + "markdown-exit": "^1.1.0-beta.2", "markdown-it-footnote": "^4.0.0", "markdown-it-github-alerts": "^1.0.1", "mlly": "^1.8.2", "monaco-editor": "^0.55.1", - "obug": "^2.1.1", + "obug": "^2.1.3", "open": "^11.0.0", + "pathe": "^2.0.3", "pdf-lib": "^1.17.1", - "picomatch": "^4.0.4", + "picomatch": "^4.0.5", "plantuml-encoder": "^1.4.0", "postcss-nested": "^7.0.2", "pptxgenjs": "^4.0.1", @@ -9781,27 +9408,28 @@ "public-ip": "^8.0.0", "resolve-from": "^5.0.0", "resolve-global": "^2.0.0", - "semver": "^7.8.1", - "shiki": "^4.2.0", + "semver": "^7.8.5", + "shiki": "^4.3.1", "sirv": "^3.0.2", "source-map-js": "^1.2.1", "typescript": "^6.0.3", - "unhead": "^3.1.1", - "unocss": "^66.7.0", + "unhead": "^3.1.7", + "unocss": "^66.7.5", "unplugin-icons": "^23.0.1", "unplugin-vue-components": "^32.1.0", "unplugin-vue-markdown": "^32.0.0", "untun": "^0.1.3", "uqr": "^0.1.3", - "vite": "^8.0.16", + "vite": "^8.1.3", "vite-plugin-inspect": "^11.4.1", "vite-plugin-remote-assets": "^2.1.0", - "vite-plugin-static-copy": "^4.1.0", + "vite-plugin-static-copy": "^4.1.1", "vite-plugin-vue-server-ref": "^1.0.0", "vitefu": "^1.1.3", - "vue": "^3.5.35", + "vue": "^3.5.39", "yaml": "^2.9.0", - "yargs": "^18.0.0" + "yargs": "^18.0.0", + "zod": "^3.25.76" }, "bin": { "slidev": "bin/slidev.mjs" @@ -9813,3388 +9441,931 @@ "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "playwright-chromium": "^1.10.0" + "playwright-chromium": "^1.10.0", + "vite-plugin-pwa": "^1.0.0" }, "peerDependenciesMeta": { "playwright-chromium": { "optional": true + }, + "vite-plugin-pwa": { + "optional": true } } }, - "node_modules/@slidev/cli/node_modules/@types/node": { - "version": "25.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", - "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", + "node_modules/@slidev/cli/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "undici-types": "~7.19.0" + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@slidev/cli/node_modules/@unocss/vite": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/vite/-/vite-66.7.2.tgz", - "integrity": "sha512-KZL8LFNcoOjAaF8AKSUJznxrjcmuQKPSAmwvndL5RjEWtbyunV66YvOuoBPN3F0tR7MhY5NWKJjwmaYyetcM1Q==", + "node_modules/@slidev/cli/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "@unocss/config": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/inspector": "66.7.2", - "chokidar": "^5.0.0", - "magic-string": "^0.30.21", - "pathe": "^2.0.3", - "tinyglobby": "^0.2.16", - "unplugin-utils": "^0.3.1" + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@slidev/cli/node_modules/@vitejs/plugin-vue": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", - "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "node_modules/@slidev/cli/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/@slidev/cli/node_modules/@vitejs/plugin-vue-jsx": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.5.tgz", - "integrity": "sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA==", + "node_modules/@slidev/cli/node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "license": "MIT", "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-syntax-typescript": "^7.28.6", - "@babel/plugin-transform-typescript": "^7.28.6", - "@rolldown/pluginutils": "^1.0.0-rc.2", - "@vue/babel-plugin-jsx": "^2.0.1" + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20" }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@slidev/cli/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@slidev/cli/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@slidev/cli/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==", + "node_modules/@slidev/client": { + "version": "52.18.0", + "resolved": "https://registry.npmjs.org/@slidev/client/-/client-52.18.0.tgz", + "integrity": "sha512-fkrmp7WheTycnmdYD22nYN1Tn9Ln3VO92j8MRnDDmGnqlyK86dCTN60JkCEpZjRalZIScKl8ELYSjOYCTrCLng==", "license": "MIT", + "dependencies": { + "@antfu/utils": "^9.3.0", + "@fix-webm-duration/fix": "^1.0.1", + "@iconify-json/carbon": "^1.2.24", + "@iconify-json/ph": "^1.2.2", + "@iconify-json/svg-spinners": "^1.2.4", + "@shikijs/engine-javascript": "^4.3.1", + "@shikijs/magic-move": "^4.3.1", + "@shikijs/monaco": "^4.3.1", + "@shikijs/vitepress-twoslash": "^4.3.1", + "@slidev/parser": "52.18.0", + "@slidev/rough-notation": "^0.1.0", + "@slidev/types": "52.18.0", + "@typescript/ata": "^0.9.8", + "@unhead/vue": "^3.1.7", + "@unocss/extractor-mdc": "^66.7.5", + "@unocss/preset-mini": "^66.7.5", + "@unocss/reset": "^66.7.5", + "@vueuse/core": "^14.3.0", + "@vueuse/math": "^14.3.0", + "@vueuse/motion": "^3.0.3", + "ansis": "^4.3.1", + "drauu": "^1.0.0", + "file-saver": "^2.0.5", + "fitty": "^2.4.2", + "floating-vue": "^5.2.2", + "fuse.js": "^7.4.2", + "katex": "^0.17.0", + "lz-string": "^1.5.0", + "mermaid": "^11.16.0", + "monaco-editor": "^0.55.1", + "nanotar": "^0.3.0", + "pptxgenjs": "^4.0.1", + "recordrtc": "^5.6.2", + "shiki": "^4.3.1", + "typescript": "^6.0.3", + "unocss": "^66.7.5", + "vue": "^3.5.39", + "vue-router": "^5.1.0", + "yaml": "^2.9.0" + }, "engines": { - "node": ">=12" + "node": ">=20.12.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@slidev/cli/node_modules/ansis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", - "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", - "license": "ISC", - "engines": { - "node": ">=14" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@slidev/cli/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "node_modules/@slidev/parser": { + "version": "52.18.0", + "resolved": "https://registry.npmjs.org/@slidev/parser/-/parser-52.18.0.tgz", + "integrity": "sha512-EaEmOCfY+Cx3OwpHlis1mc0FoNolFdUqXYms8dvmhD5/hoI9LYQUbJZxL7ajMb/XAs/REdl9Ovpqqzz0S1bACg==", "license": "MIT", "dependencies": { - "readdirp": "^5.0.0" + "@antfu/utils": "^9.3.0", + "@slidev/types": "52.18.0", + "pathe": "^2.0.3", + "yaml": "^2.9.0" }, "engines": { - "node": ">= 20.19.0" + "node": ">=20.12.0" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@slidev/cli/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", + "node_modules/@slidev/rough-notation": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@slidev/rough-notation/-/rough-notation-0.1.0.tgz", + "integrity": "sha512-a/CbVmjuoO3E4JbUr2HOTsXndbcrdLWOM+ajbSQIY3gmLFzhjeXHGksGcp1NZ08pJjLZyTCxfz1C7v/ltJqycA==", + "license": "MIT", "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" + "roughjs": "^4.6.6" } }, - "node_modules/@slidev/cli/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@slidev/theme-default": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@slidev/theme-default/-/theme-default-0.25.0.tgz", + "integrity": "sha512-iWvthH1Ny+i6gTwRnEeeU+EiqsHC56UdEO45bqLSNmymRAOWkKUJ/M0o7iahLzHSXsiPu71B7C715WxqjXk2hw==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@slidev/types": "^0.47.0", + "codemirror-theme-vars": "^0.1.2", + "prism-theme-vars": "^0.2.4" }, "engines": { - "node": ">=6.0" + "node": ">=14.0.0", + "slidev": ">=v0.47.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@slidev/cli/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/@slidev/theme-default/node_modules/@slidev/types": { + "version": "0.47.5", + "resolved": "https://registry.npmjs.org/@slidev/types/-/types-0.47.5.tgz", + "integrity": "sha512-X67V4cCgM0Sz50bP8GbVzmiL8DHC2IXvdKcsN7DlxHyf+/T4d9GveeGukwha5Fx3MuYeGZWKag7TFL2ZY4w54A==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@slidev/cli/node_modules/dom-serializer": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", - "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "node_modules/@slidev/types": { + "version": "52.18.0", + "resolved": "https://registry.npmjs.org/@slidev/types/-/types-52.18.0.tgz", + "integrity": "sha512-bnLTZFVvURCqVeH0bm+He22Xfvk3CxpKY4QLg6i4TlbWDX0tg6nDzJWcufdbnEueJPoQOQwgax0SNEN8xVqXkw==", "license": "MIT", "dependencies": { - "domelementtype": "^3.0.0", - "domhandler": "^6.0.0", - "entities": "^8.0.0" + "@antfu/utils": "^9.3.0", + "@shikijs/markdown-it": "^4.3.1", + "@vitejs/plugin-vue": "^6.0.7", + "@vitejs/plugin-vue-jsx": "^5.1.6", + "katex": "^0.17.0", + "mermaid": "^11.16.0", + "monaco-editor": "^0.55.1", + "shiki": "^4.3.1", + "unocss": "^66.7.5", + "unplugin-icons": "^23.0.1", + "unplugin-vue-markdown": "^32.0.0", + "vite-plugin-inspect": "^11.4.1", + "vite-plugin-remote-assets": "^2.1.0", + "vite-plugin-static-copy": "^4.1.1", + "vite-plugin-vue-server-ref": "^1.0.0", + "vue": "^3.5.39", + "vue-router": "^5.1.0" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.12.0" }, "funding": { - "type": "github", - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/@slidev/cli/node_modules/domelementtype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", - "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause", - "engines": { - "node": ">=20.19.0" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@slidev/cli/node_modules/domhandler": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", - "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", - "license": "BSD-2-Clause", + "node_modules/@slorber/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "dev": true, + "license": "MIT", "dependencies": { - "domelementtype": "^3.0.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/fb55/domhandler?sponsor=1" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" } }, - "node_modules/@slidev/cli/node_modules/domutils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", - "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^3.0.0", - "domelementtype": "^3.0.0", - "domhandler": "^6.0.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/fb55/domutils?sponsor=1" - } + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, - "node_modules/@slidev/cli/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==", + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", "license": "MIT" }, - "node_modules/@slidev/cli/node_modules/entities": { + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=14" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@slidev/cli/node_modules/htmlparser2": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz", - "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^3.0.0", - "domhandler": "^6.0.0", - "domutils": "^4.0.2", - "entities": "^8.0.0" + "type": "github", + "url": "https://github.com/sponsors/gregberge" }, - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@slidev/cli/node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/is-installed-globally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", - "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", "license": "MIT", - "dependencies": { - "global-directory": "^4.0.1", - "is-path-inside": "^4.0.0" - }, "engines": { - "node": ">=18" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/is-installed-globally/node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, "engines": { - "node": ">=18" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, "engines": { - "node": ">=16" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@slidev/cli/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/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/@slidev/cli/node_modules/open": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "license": "MIT", - "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, "engines": { - "node": ">=20" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, "engines": { - "node": ">= 20.19.0" + "node": ">=14" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@slidev/cli/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@slidev/cli/node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "license": "MIT", "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" + "@babel/types": "^7.21.3", + "entities": "^4.4.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@slidev/cli/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" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@slidev/cli/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@slidev/cli/node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@slidev/cli/node_modules/unhead": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/unhead/-/unhead-3.1.4.tgz", - "integrity": "sha512-Whwejfc9dHnzgRkYoOxmDc3wsGtBR+PhdKpVr2neLJmeFfz5RmvrnLK5ctixwJ6ttUBYucmeGWS+0j68I+9WeQ==", - "license": "MIT", - "dependencies": { - "hookable": "^6.1.1", - "unplugin": "^3.0.0" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/harlan-zw" + "type": "github", + "url": "https://github.com/sponsors/gregberge" }, "peerDependencies": { - "vite": ">=6.4.2" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } + "@svgr/core": "*" } }, - "node_modules/@slidev/cli/node_modules/unocss": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/unocss/-/unocss-66.7.2.tgz", - "integrity": "sha512-yB0yOpJTtlyGH/HAe4QdnjgjSP6z9ItTdrObvagc8ZEwRY1D2GbfUABwDKyZzXs19gXebqThMG9f+W0hPhDIPA==", + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, "license": "MIT", "dependencies": { - "@unocss/cli": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/preset-attributify": "66.7.2", - "@unocss/preset-icons": "66.7.2", - "@unocss/preset-mini": "66.7.2", - "@unocss/preset-tagify": "66.7.2", - "@unocss/preset-typography": "66.7.2", - "@unocss/preset-uno": "66.7.2", - "@unocss/preset-web-fonts": "66.7.2", - "@unocss/preset-wind": "66.7.2", - "@unocss/preset-wind3": "66.7.2", - "@unocss/preset-wind4": "66.7.2", - "@unocss/transformer-attributify-jsx": "66.7.2", - "@unocss/transformer-compile-class": "66.7.2", - "@unocss/transformer-directives": "66.7.2", - "@unocss/transformer-variant-group": "66.7.2", - "@unocss/vite": "66.7.2" + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "github", + "url": "https://github.com/sponsors/gregberge" }, "peerDependencies": { - "@unocss/astro": "66.7.2", - "@unocss/postcss": "66.7.2", - "@unocss/webpack": "66.7.2" - }, - "peerDependenciesMeta": { - "@unocss/astro": { - "optional": true - }, - "@unocss/postcss": { - "optional": true - }, - "@unocss/webpack": { - "optional": true - } + "@svgr/core": "*" } }, - "node_modules/@slidev/cli/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@slidev/cli/node_modules/unplugin-vue-markdown": { - "version": "32.0.0", - "resolved": "https://registry.npmjs.org/unplugin-vue-markdown/-/unplugin-vue-markdown-32.0.0.tgz", - "integrity": "sha512-K9uiYJF9kvngrN/NRx8fVPZFXiqJR7tbnXQV4mVFxjcsKhuiL6+vVQ5woam59RqeCDprecBmbg+cCGADz0somA==", + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, "license": "MIT", "dependencies": { - "@mdit-vue/plugin-component": "^3.0.2", - "@mdit-vue/plugin-frontmatter": "^3.0.2", - "@mdit-vue/types": "^3.0.2", - "markdown-exit": "^1.0.0-beta.9", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "node": ">=14.16" } }, - "node_modules/@slidev/cli/node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "node_modules/@telegraf/types": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", + "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@slidev/cli/node_modules/vite-dev-rpc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", - "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "license": "MIT", + "optional": true, "dependencies": { - "birpc": "^4.0.0", - "vite-hot-client": "^2.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@slidev/cli/node_modules/vite-hot-client": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", - "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" + "dependencies": { + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-inspect": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", - "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, "license": "MIT", "dependencies": { - "ansis": "^4.3.0", - "error-stack-parser-es": "^1.0.5", - "obug": "^2.1.1", - "ohash": "^2.0.11", - "open": "^11.0.0", - "perfect-debounce": "^2.1.0", - "sirv": "^3.0.2", - "unplugin-utils": "^0.3.1", - "vite-dev-rpc": "^2.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } + "@types/node": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-remote-assets": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vite-plugin-remote-assets/-/vite-plugin-remote-assets-2.1.0.tgz", - "integrity": "sha512-8ajL5WG5BmYcC8zxeLOa3byCUG2AopKDAdNK7zStPHaRYYz1mxXBaeNFLu6vTEXj8UmXAsb5WlEmBBYwtlPEwA==", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.1", - "magic-string": "^0.30.17", - "node-fetch-native": "^1.6.7", - "ohash": "^2.0.11" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": ">=5.0.0" + "@types/node": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-static-copy": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-4.1.0.tgz", - "integrity": "sha512-9XOarNV7LgP0KBB7AApxdgFikLXx3daZdqjC3AevYsL6MrUH62zphonLUs2a6LZc1HN1GY+vQdheZ8VVJb6dQQ==", + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^3.6.0", - "p-map": "^7.0.4", - "picocolors": "^1.1.1", - "tinyglobby": "^0.2.15" - }, - "engines": { - "node": "^22.0.0 || >=24.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/sapphi-red" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@types/express-serve-static-core": "*", + "@types/node": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-static-copy/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "@types/node": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-static-copy/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-static-copy/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "@types/d3-selection": "*" } }, - "node_modules/@slidev/cli/node_modules/vite-plugin-vue-server-ref": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-server-ref/-/vite-plugin-vue-server-ref-1.0.0.tgz", - "integrity": "sha512-6d/JZVrnETM0xa0AVyEcI1bXFpEzQ1EPU5N/gDa7NtXo/7nfJWJhezcWq82Jih6Vf8xtGJjhi1w19AcXAtwmAg==", + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "klona": "^2.0.6", - "mlly": "^1.7.4", - "ufo": "^1.5.4" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": ">=2.0.0", - "vue": "^3.0.0" + "@types/d3-selection": "*" } }, - "node_modules/@slidev/cli/node_modules/vitefu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", - "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" } }, - "node_modules/@slidev/cli/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==", + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "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" + "@types/d3-selection": "*" } }, - "node_modules/@slidev/cli/node_modules/wsl-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", - "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "license": "MIT", "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/d3-dsv": "*" } }, - "node_modules/@slidev/cli/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "license": "MIT", "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" + "@types/geojson": "*" } }, - "node_modules/@slidev/cli/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" }, - "node_modules/@slidev/client": { - "version": "52.16.0", - "resolved": "https://registry.npmjs.org/@slidev/client/-/client-52.16.0.tgz", - "integrity": "sha512-h5oyCQTvTTZKpYjM5dma1jh0lKwUfNICgIajhl7/YKaXUJnmZwi3lp394Aatui1eOfi/gdey2lGHBbbMZEuSXQ==", + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "license": "MIT", "dependencies": { - "@antfu/utils": "^9.3.0", - "@fix-webm-duration/fix": "^1.0.1", - "@iconify-json/carbon": "^1.2.22", - "@iconify-json/ph": "^1.2.2", - "@iconify-json/svg-spinners": "^1.2.4", - "@shikijs/engine-javascript": "^4.2.0", - "@shikijs/magic-move": "^4.2.0", - "@shikijs/monaco": "^4.2.0", - "@shikijs/vitepress-twoslash": "^4.2.0", - "@slidev/parser": "52.16.0", - "@slidev/rough-notation": "^0.1.0", - "@slidev/types": "52.16.0", - "@typescript/ata": "^0.9.8", - "@unhead/vue": "^3.1.1", - "@unocss/extractor-mdc": "^66.7.0", - "@unocss/preset-mini": "^66.7.0", - "@unocss/reset": "^66.7.0", - "@vueuse/core": "^14.3.0", - "@vueuse/math": "^14.3.0", - "@vueuse/motion": "^3.0.3", - "ansis": "^4.3.1", - "drauu": "^1.0.0", - "file-saver": "^2.0.5", - "floating-vue": "^5.2.2", - "fuse.js": "^7.4.1", - "katex": "^0.17.0", - "lz-string": "^1.5.0", - "mermaid": "^11.15.0", - "monaco-editor": "^0.55.1", - "nanotar": "^0.3.0", - "pptxgenjs": "^4.0.1", - "recordrtc": "^5.6.2", - "shiki": "^4.2.0", - "typescript": "^6.0.3", - "unocss": "^66.7.0", - "vue": "^3.5.35", - "vue-router": "^5.1.0", - "yaml": "^2.9.0" - }, - "engines": { - "node": ">=20.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "@types/d3-color": "*" } }, - "node_modules/@slidev/client/node_modules/@babel/generator": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", - "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", "dependencies": { - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "@types/jsesc": "^2.5.0", - "jsesc": "^3.0.2" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" + "@types/d3-time": "*" } }, - "node_modules/@slidev/client/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" + "dependencies": { + "@types/d3-path": "*" } }, - "node_modules/@slidev/client/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0.tgz", - "integrity": "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g==", + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" + "dependencies": { + "@types/d3-selection": "*" } }, - "node_modules/@slidev/client/node_modules/@babel/parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0.tgz", - "integrity": "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==", + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "license": "MIT", "dependencies": { - "@babel/types": "^8.0.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" } }, - "node_modules/@slidev/client/node_modules/@babel/types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0.tgz", - "integrity": "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==", + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" + "@types/ms": "*" } }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.135.0.tgz", - "integrity": "sha512-sHeZItACNcA5WRAWqF6ixriR4GkZDyY10gVgnZU7pXku1DjHFATSqnwZM809jl0gXPHxb6fKzYQCK7bNK5cACQ==", - "cpu": [ - "arm" - ], + "node_modules/@types/dom-mediacapture-transform": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.12.tgz", + "integrity": "sha512-d7/QsLRwF864A5mgIM/YrfiglHoYn7zgCcAoJgW404r+2DwnNr7EBbLnCWpmOMgH8y0te73L1AV6H1bmauaWFw==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@types/dom-webcodecs": "*" } }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.135.0.tgz", - "integrity": "sha512-wPte+SzgzWWFgMSF8YZDNM+tBXtJg0AXBi7+tU3yS2z1f2Af9kRLZLKuJojADmuD/cZexmnMHHC3SDItTW77Iw==", - "cpu": [ - "arm64" - ], + "node_modules/@types/dom-webcodecs": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", + "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.135.0.tgz", - "integrity": "sha512-BmKz3lHIsqVos+9aPcdYCT9MG3APoUyM43KlEFhJMWNVDOGG8FKyiFz81Bc+mGz2o0hpuQ3PfXLfVWJrKXjo2g==", - "cpu": [ - "arm64" - ], + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.135.0.tgz", - "integrity": "sha512-dM8BS+8+Br1fNvmh2QZbGiHaYttwLebRa6J4Uz9vuFzMNmvsdRYwf7993ptOaV0JTrR63AaoVLjX7nhWbijxjQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.135.0.tgz", - "integrity": "sha512-xlZnvvJdR9bGu2pOhvR5hMuKPHCE6Sa9owK5A484mzjHdm75VRV5nCs5w/jkmGODMMTFc+KN7EnZqEieM813kw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.135.0.tgz", - "integrity": "sha512-PSR8LmBK/H/PQRiN8g7RebQgZX/ntVCrdT/JBfNxE5ezdHG1s2i4rbazsRJYD83TTI1MmgTpC0MGL42PLtskQQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.135.0.tgz", - "integrity": "sha512-I85GJXzfUsigkkk7Ngdz95C217M4FdUi1Z2HrX5UyPmURobwQZ7m2bbUvwFkz4VGZd+lymFGKHvDZ3RQC9qOzA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.135.0.tgz", - "integrity": "sha512-zqEY0npz0g0aGZj/8a5BclunjVDytsBQHYtIC10Gd26HcrLwbVF6YDbqRQjunMGYdSo97u6xOBl05aTDI2diDQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.135.0.tgz", - "integrity": "sha512-mWAfprP819gQ2qYst1RxgTI8b/z0b29OpoKfRflIXLHde2dZLihQD4g47Onuvtpo5GPIkMYPRlX9QoeZfs/GnQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.135.0.tgz", - "integrity": "sha512-gri8c2AOmJKJwOux2KTHFBfUaXoJURuVMKhmKEi/2hTF55cQteTDV2XNfTiE5oCC+Tnem1Y4/MWzcyDadtsSag==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.135.0.tgz", - "integrity": "sha512-Y2tkupCG5wo0SxH2rMLG4d4Kmv6DaM3sBp+GuM5lox0S8Za6VxKgQrY2Mut088QQxKkEE89n/4CCCgmw2o0e3Q==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.135.0.tgz", - "integrity": "sha512-xDRJq6i6WTynjeP+ISbDpyH4p9BaJ0wuQcL0lCSDkt9qOXC9dmwpOu1VG/TlwmPI3KpYntmO9nJCuc3TMTsNBA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.135.0.tgz", - "integrity": "sha512-V4MoUuiCRNvihxhIufRxvK+ka013V4joTSK0FAGA1KEjLuNprfH6N/Qw2uxQEVIFuNYMhD/hV6xJ/ptbzlKdHg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.135.0.tgz", - "integrity": "sha512-JCFZ7zM7KXOKoPAbK/ZB4wY0M1jxRECiem2UQuiXLjzGqS9+hno7mtX+qyK2F7HWK2xPhyJb+frpcOtk5DKOtg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.135.0.tgz", - "integrity": "sha512-9jSVS1b3hOV7sdKH4aA2DFfnTz0RgQd0v2BefR+LYbH8yIlmSM22JJZbAAjVeVXmFgUAk3zJQ1tpE/Nd+Vi2YQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.135.0.tgz", - "integrity": "sha512-M857ZLBSdn1Uy/SJJz5zh0qGu67B4P9omCgXGBU2LLqTzraX6ZjVNaKq5yW1PDw/LgJXDXR/dbZfgmB310f11Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.135.0.tgz", - "integrity": "sha512-2w6DVcntQZX9U5RhXtgiWb3FLWFB5EcwI1U8yr3htOCJUJjagN4BFUHz/Y/d9ZsumndZ6ByxxWEtbUZNE1bfFw==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.135.0.tgz", - "integrity": "sha512-rX1U8+IH2Z37EJjDXKa1iifvUQAdba+vZ4Ewj1iaG5eA/QaSybzclCOwtWa0/5BuUQnnK/T2JHUEFrwhL6Ck2Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.135.0.tgz", - "integrity": "sha512-9FAisBbH1QICGAjlJobiuKGd/jOuVmyqniWdQMwTa5SkCl6hhuotBCJf1n46B0flYbSOR5TzfV9HZCWSyb3c/Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.135.0.tgz", - "integrity": "sha512-wYF+A2AzJ2n7ul6q+Z2G/ia0S2+8cUp0AgWZzoFvF4WmUcl1P7p+o6se1Gdr5wGnWuF0iAMIkGddrjCarNr2yA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/@oxc-project/types": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.135.0.tgz", - "integrity": "sha512-wR+xRdFkUBMvcAjBJ2q2kcZM6d+DKu2NgoOyxZgYwZdLhmiv6+rnO8PZ/P68kMiZtIKm+pW7zyEJ4kSOs0vo+Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@slidev/client/node_modules/@types/node": { - "version": "25.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", - "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, - "node_modules/@slidev/client/node_modules/@unhead/bundler": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@unhead/bundler/-/bundler-3.1.4.tgz", - "integrity": "sha512-frpo3wuXtR/eeI9PmyjNGb+AMFxY36v7vcHTt3yKoLDJVxnsfeYgRjk+F2AOIVLctr4uL5Uqrf21cLEWHkZkkg==", - "license": "MIT", - "dependencies": { - "@vitejs/devtools-kit": "^0.3.1", - "magic-string": "^0.30.21", - "oxc-parser": "^0.135.0", - "oxc-walker": "^1.0.0", - "ufo": "^1.6.4", - "unplugin": "^3.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/harlan-zw" - }, - "peerDependencies": { - "@unhead/cli": "^3.1.4", - "esbuild": ">=0.17.0", - "lightningcss": ">=1.20.0", - "rolldown": ">=1.0.0-beta.0", - "unhead": "^3.1.4", - "vite": ">=6.4.2", - "webpack": ">=5.0.0" - }, - "peerDependenciesMeta": { - "@unhead/cli": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "rolldown": { - "optional": true - }, - "vite": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/@slidev/client/node_modules/@unhead/bundler/node_modules/oxc-walker": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/oxc-walker/-/oxc-walker-1.0.0.tgz", - "integrity": "sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ==", - "license": "MIT", - "dependencies": { - "magic-regexp": "^0.11.0" - }, - "peerDependencies": { - "oxc-parser": ">=0.98.0", - "rolldown": ">=1.0.0" - }, - "peerDependenciesMeta": { - "oxc-parser": { - "optional": true - }, - "rolldown": { - "optional": true - } - } - }, - "node_modules/@slidev/client/node_modules/@unhead/vue": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-3.1.4.tgz", - "integrity": "sha512-+QMRhzVtI3BjZSRtAQRnVuje0IP7zBdEfZfQt7C8OJhY0EiHCECEgxAEk+p7HJPYxQeToV0GYrnzzzIZEas99A==", - "license": "MIT", - "dependencies": { - "@unhead/bundler": "3.1.4", - "hookable": "^6.1.1", - "unhead": "3.1.4", - "unplugin": "^3.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/harlan-zw" - }, - "peerDependencies": { - "vite": ">=6.4.2", - "vue": ">=3.5.18", - "webpack": ">=5.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/@slidev/client/node_modules/@unocss/vite": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/vite/-/vite-66.7.2.tgz", - "integrity": "sha512-KZL8LFNcoOjAaF8AKSUJznxrjcmuQKPSAmwvndL5RjEWtbyunV66YvOuoBPN3F0tR7MhY5NWKJjwmaYyetcM1Q==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "@unocss/config": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/inspector": "66.7.2", - "chokidar": "^5.0.0", - "magic-string": "^0.30.21", - "pathe": "^2.0.3", - "tinyglobby": "^0.2.16", - "unplugin-utils": "^0.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" - } - }, - "node_modules/@slidev/client/node_modules/@vitejs/devtools-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/devtools-kit/-/devtools-kit-0.3.3.tgz", - "integrity": "sha512-noXyK0szYxh8ALsA7PIoFANOWx13K9NoMKqk3J1pCwGMdnhxWgSqtbhki+/REoK4itbxIFUfc0EwqsoZIKiqyg==", - "license": "MIT", - "dependencies": { - "@devframes/hub": "^0.5.4", - "birpc": "^4.0.0", - "devframe": "^0.5.4", - "mlly": "^1.8.2", - "nostics": "^0.3.0", - "pathe": "^2.0.3", - "perfect-debounce": "^2.1.0", - "tinyexec": "^1.2.4" - }, - "peerDependencies": { - "vite": "*" - } - }, - "node_modules/@slidev/client/node_modules/ansis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", - "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "node_modules/@slidev/client/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@slidev/client/node_modules/magic-regexp": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.11.0.tgz", - "integrity": "sha512-LG77Z/gVnwz7oaDpD4heX6ryl+lcr4l1B2gnP4MMvt2pGhGC1Dfj7dl1pXpP4ih+VQFLuAadeKVa+lARAzfW+Q==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.21", - "regexp-tree": "^0.1.27", - "type-level-regexp": "~0.1.17", - "unplugin": "^3.0.0" - } - }, - "node_modules/@slidev/client/node_modules/oxc-parser": { - "version": "0.135.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.135.0.tgz", - "integrity": "sha512-/DaPStu0s2zzNSRRniKyTPM6Z/o+DapOp2JYNKDL8AsgaBGPK2IdZyB87SQjVH+xeQPz+Qr9mrjglfkYgtbVRA==", - "license": "MIT", - "dependencies": { - "@oxc-project/types": "^0.135.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.135.0", - "@oxc-parser/binding-android-arm64": "0.135.0", - "@oxc-parser/binding-darwin-arm64": "0.135.0", - "@oxc-parser/binding-darwin-x64": "0.135.0", - "@oxc-parser/binding-freebsd-x64": "0.135.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.135.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.135.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.135.0", - "@oxc-parser/binding-linux-arm64-musl": "0.135.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.135.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.135.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.135.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.135.0", - "@oxc-parser/binding-linux-x64-gnu": "0.135.0", - "@oxc-parser/binding-linux-x64-musl": "0.135.0", - "@oxc-parser/binding-openharmony-arm64": "0.135.0", - "@oxc-parser/binding-wasm32-wasi": "0.135.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.135.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.135.0", - "@oxc-parser/binding-win32-x64-msvc": "0.135.0" - } - }, - "node_modules/@slidev/client/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@slidev/client/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@slidev/client/node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@slidev/client/node_modules/unhead": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/unhead/-/unhead-3.1.4.tgz", - "integrity": "sha512-Whwejfc9dHnzgRkYoOxmDc3wsGtBR+PhdKpVr2neLJmeFfz5RmvrnLK5ctixwJ6ttUBYucmeGWS+0j68I+9WeQ==", - "license": "MIT", - "dependencies": { - "hookable": "^6.1.1", - "unplugin": "^3.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/harlan-zw" - }, - "peerDependencies": { - "vite": ">=6.4.2" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/@slidev/client/node_modules/unocss": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/unocss/-/unocss-66.7.2.tgz", - "integrity": "sha512-yB0yOpJTtlyGH/HAe4QdnjgjSP6z9ItTdrObvagc8ZEwRY1D2GbfUABwDKyZzXs19gXebqThMG9f+W0hPhDIPA==", - "license": "MIT", - "dependencies": { - "@unocss/cli": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/preset-attributify": "66.7.2", - "@unocss/preset-icons": "66.7.2", - "@unocss/preset-mini": "66.7.2", - "@unocss/preset-tagify": "66.7.2", - "@unocss/preset-typography": "66.7.2", - "@unocss/preset-uno": "66.7.2", - "@unocss/preset-web-fonts": "66.7.2", - "@unocss/preset-wind": "66.7.2", - "@unocss/preset-wind3": "66.7.2", - "@unocss/preset-wind4": "66.7.2", - "@unocss/transformer-attributify-jsx": "66.7.2", - "@unocss/transformer-compile-class": "66.7.2", - "@unocss/transformer-directives": "66.7.2", - "@unocss/transformer-variant-group": "66.7.2", - "@unocss/vite": "66.7.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@unocss/astro": "66.7.2", - "@unocss/postcss": "66.7.2", - "@unocss/webpack": "66.7.2" - }, - "peerDependenciesMeta": { - "@unocss/astro": { - "optional": true - }, - "@unocss/postcss": { - "optional": true - }, - "@unocss/webpack": { - "optional": true - } - } - }, - "node_modules/@slidev/client/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/client/node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "license": "MIT", - "peer": true, - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@slidev/client/node_modules/vue-router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", - "integrity": "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^8.0.0-rc.4", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.1.2", - "ast-walker-scope": "^0.9.0", - "chokidar": "^5.0.0", - "json5": "^2.2.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.2", - "muggle-string": "^0.4.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "scule": "^1.3.0", - "tinyglobby": "^0.2.16", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1", - "yaml": "^2.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "@pinia/colada": ">=0.21.2", - "@vue/compiler-sfc": "^3.5.34", - "pinia": "^3.0.4", - "vite": "^7.0.0 || ^8.0.0", - "vue": "^3.5.34" - }, - "peerDependenciesMeta": { - "@pinia/colada": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "pinia": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@slidev/parser": { - "version": "52.16.0", - "resolved": "https://registry.npmjs.org/@slidev/parser/-/parser-52.16.0.tgz", - "integrity": "sha512-xJuaFXmWwBbvSnQ0Zu4r9pk12sV3CAmHBUzkMLAiyYDPcQpgo7rNPUsy2y216E6U0YexuZyBlkTRmJD1GAaXTg==", - "license": "MIT", - "dependencies": { - "@antfu/utils": "^9.3.0", - "@slidev/types": "52.16.0", - "yaml": "^2.9.0" - }, - "engines": { - "node": ">=20.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@slidev/rough-notation": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@slidev/rough-notation/-/rough-notation-0.1.0.tgz", - "integrity": "sha512-a/CbVmjuoO3E4JbUr2HOTsXndbcrdLWOM+ajbSQIY3gmLFzhjeXHGksGcp1NZ08pJjLZyTCxfz1C7v/ltJqycA==", - "license": "MIT", - "dependencies": { - "roughjs": "^4.6.6" - } - }, - "node_modules/@slidev/theme-default": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@slidev/theme-default/-/theme-default-0.25.0.tgz", - "integrity": "sha512-iWvthH1Ny+i6gTwRnEeeU+EiqsHC56UdEO45bqLSNmymRAOWkKUJ/M0o7iahLzHSXsiPu71B7C715WxqjXk2hw==", - "license": "MIT", - "dependencies": { - "@slidev/types": "^0.47.0", - "codemirror-theme-vars": "^0.1.2", - "prism-theme-vars": "^0.2.4" - }, - "engines": { - "node": ">=14.0.0", - "slidev": ">=v0.47.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@slidev/theme-default/node_modules/@slidev/types": { - "version": "0.47.5", - "resolved": "https://registry.npmjs.org/@slidev/types/-/types-0.47.5.tgz", - "integrity": "sha512-X67V4cCgM0Sz50bP8GbVzmiL8DHC2IXvdKcsN7DlxHyf+/T4d9GveeGukwha5Fx3MuYeGZWKag7TFL2ZY4w54A==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@slidev/types": { - "version": "52.16.0", - "resolved": "https://registry.npmjs.org/@slidev/types/-/types-52.16.0.tgz", - "integrity": "sha512-BCRP7CS/jHajhIIwmHjVykgFsETbR9+rDHzPKS6mHAB32Xjijag8i+wzmk87ct/4cSxq39YDZi4lVPuNFuSCxQ==", - "license": "MIT", - "dependencies": { - "@antfu/utils": "^9.3.0", - "@shikijs/markdown-it": "^4.2.0", - "@vitejs/plugin-vue": "^6.0.7", - "@vitejs/plugin-vue-jsx": "^5.1.5", - "katex": "^0.17.0", - "mermaid": "^11.15.0", - "monaco-editor": "^0.55.1", - "shiki": "^4.2.0", - "unocss": "^66.7.0", - "unplugin-icons": "^23.0.1", - "unplugin-vue-markdown": "^32.0.0", - "vite-plugin-inspect": "^11.4.1", - "vite-plugin-remote-assets": "^2.1.0", - "vite-plugin-static-copy": "^4.1.0", - "vite-plugin-vue-server-ref": "^1.0.0", - "vue": "^3.5.35", - "vue-router": "^5.1.0" - }, - "engines": { - "node": ">=20.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@slidev/types/node_modules/@babel/generator": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", - "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "@types/jsesc": "^2.5.0", - "jsesc": "^3.0.2" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@slidev/types/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@slidev/types/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0.tgz", - "integrity": "sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@slidev/types/node_modules/@babel/parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0.tgz", - "integrity": "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@slidev/types/node_modules/@babel/types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0.tgz", - "integrity": "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@slidev/types/node_modules/@types/node": { - "version": "25.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", - "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, - "node_modules/@slidev/types/node_modules/@unocss/vite": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/vite/-/vite-66.7.2.tgz", - "integrity": "sha512-KZL8LFNcoOjAaF8AKSUJznxrjcmuQKPSAmwvndL5RjEWtbyunV66YvOuoBPN3F0tR7MhY5NWKJjwmaYyetcM1Q==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "@unocss/config": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/inspector": "66.7.2", - "chokidar": "^5.0.0", - "magic-string": "^0.30.21", - "pathe": "^2.0.3", - "tinyglobby": "^0.2.16", - "unplugin-utils": "^0.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" - } - }, - "node_modules/@slidev/types/node_modules/@unocss/vite/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@slidev/types/node_modules/@vitejs/plugin-vue": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", - "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@slidev/types/node_modules/@vitejs/plugin-vue-jsx": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.5.tgz", - "integrity": "sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-syntax-typescript": "^7.28.6", - "@babel/plugin-transform-typescript": "^7.28.6", - "@rolldown/pluginutils": "^1.0.0-rc.2", - "@vue/babel-plugin-jsx": "^2.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.0.0" - } - }, - "node_modules/@slidev/types/node_modules/ansis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", - "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "node_modules/@slidev/types/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/@slidev/types/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@slidev/types/node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@slidev/types/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/@slidev/types/node_modules/open": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@slidev/types/node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@slidev/types/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@slidev/types/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@slidev/types/node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@slidev/types/node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@slidev/types/node_modules/unocss": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/unocss/-/unocss-66.7.2.tgz", - "integrity": "sha512-yB0yOpJTtlyGH/HAe4QdnjgjSP6z9ItTdrObvagc8ZEwRY1D2GbfUABwDKyZzXs19gXebqThMG9f+W0hPhDIPA==", - "license": "MIT", - "dependencies": { - "@unocss/cli": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/preset-attributify": "66.7.2", - "@unocss/preset-icons": "66.7.2", - "@unocss/preset-mini": "66.7.2", - "@unocss/preset-tagify": "66.7.2", - "@unocss/preset-typography": "66.7.2", - "@unocss/preset-uno": "66.7.2", - "@unocss/preset-web-fonts": "66.7.2", - "@unocss/preset-wind": "66.7.2", - "@unocss/preset-wind3": "66.7.2", - "@unocss/preset-wind4": "66.7.2", - "@unocss/transformer-attributify-jsx": "66.7.2", - "@unocss/transformer-compile-class": "66.7.2", - "@unocss/transformer-directives": "66.7.2", - "@unocss/transformer-variant-group": "66.7.2", - "@unocss/vite": "66.7.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@unocss/astro": "66.7.2", - "@unocss/postcss": "66.7.2", - "@unocss/webpack": "66.7.2" - }, - "peerDependenciesMeta": { - "@unocss/astro": { - "optional": true - }, - "@unocss/postcss": { - "optional": true - }, - "@unocss/webpack": { - "optional": true - } - } - }, - "node_modules/@slidev/types/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@slidev/types/node_modules/unplugin-vue-markdown": { - "version": "32.0.0", - "resolved": "https://registry.npmjs.org/unplugin-vue-markdown/-/unplugin-vue-markdown-32.0.0.tgz", - "integrity": "sha512-K9uiYJF9kvngrN/NRx8fVPZFXiqJR7tbnXQV4mVFxjcsKhuiL6+vVQ5woam59RqeCDprecBmbg+cCGADz0somA==", - "license": "MIT", - "dependencies": { - "@mdit-vue/plugin-component": "^3.0.2", - "@mdit-vue/plugin-frontmatter": "^3.0.2", - "@mdit-vue/types": "^3.0.2", - "markdown-exit": "^1.0.0-beta.9", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/@slidev/types/node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "license": "MIT", - "peer": true, - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@slidev/types/node_modules/vite-dev-rpc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", - "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", - "license": "MIT", - "dependencies": { - "birpc": "^4.0.0", - "vite-hot-client": "^2.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" - } - }, - "node_modules/@slidev/types/node_modules/vite-hot-client": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", - "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" - } - }, - "node_modules/@slidev/types/node_modules/vite-plugin-inspect": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", - "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", - "license": "MIT", - "dependencies": { - "ansis": "^4.3.0", - "error-stack-parser-es": "^1.0.5", - "obug": "^2.1.1", - "ohash": "^2.0.11", - "open": "^11.0.0", - "perfect-debounce": "^2.1.0", - "sirv": "^3.0.2", - "unplugin-utils": "^0.3.1", - "vite-dev-rpc": "^2.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/@slidev/types/node_modules/vite-plugin-remote-assets": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vite-plugin-remote-assets/-/vite-plugin-remote-assets-2.1.0.tgz", - "integrity": "sha512-8ajL5WG5BmYcC8zxeLOa3byCUG2AopKDAdNK7zStPHaRYYz1mxXBaeNFLu6vTEXj8UmXAsb5WlEmBBYwtlPEwA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.1", - "magic-string": "^0.30.17", - "node-fetch-native": "^1.6.7", - "ohash": "^2.0.11" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": ">=5.0.0" - } - }, - "node_modules/@slidev/types/node_modules/vite-plugin-static-copy": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-4.1.1.tgz", - "integrity": "sha512-GrlA8YklrAfSyxJ4M3fdQLOo9oNkp56IM9FYgX/WtEgeIFkPwhu4wzpufBCIuNKCa6Fn77FkRdYxkHqV0FwjAw==", - "license": "MIT", - "dependencies": { - "chokidar": "^3.6.0", - "p-map": "^7.0.4", - "picocolors": "^1.1.1", - "tinyglobby": "^0.2.17" - }, - "engines": { - "node": "^22.0.0 || >=24.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/sapphi-red" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@slidev/types/node_modules/vite-plugin-vue-server-ref": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-server-ref/-/vite-plugin-vue-server-ref-1.0.0.tgz", - "integrity": "sha512-6d/JZVrnETM0xa0AVyEcI1bXFpEzQ1EPU5N/gDa7NtXo/7nfJWJhezcWq82Jih6Vf8xtGJjhi1w19AcXAtwmAg==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "klona": "^2.0.6", - "mlly": "^1.7.4", - "ufo": "^1.5.4" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": ">=2.0.0", - "vue": "^3.0.0" - } - }, - "node_modules/@slidev/types/node_modules/vue-router": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", - "integrity": "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^8.0.0-rc.4", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.1.2", - "ast-walker-scope": "^0.9.0", - "chokidar": "^5.0.0", - "json5": "^2.2.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.2", - "muggle-string": "^0.4.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "scule": "^1.3.0", - "tinyglobby": "^0.2.16", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1", - "yaml": "^2.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "@pinia/colada": ">=0.21.2", - "@vue/compiler-sfc": "^3.5.34", - "pinia": "^3.0.4", - "vite": "^7.0.0 || ^8.0.0", - "vue": "^3.5.34" - }, - "peerDependenciesMeta": { - "@pinia/colada": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "pinia": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@slidev/types/node_modules/vue-router/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@slidev/types/node_modules/wsl-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", - "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@slorber/remark-comment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", - "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.1.0", - "micromark-util-symbol": "^1.0.1" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, - "node_modules/@stablelib/base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", - "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", - "license": "MIT" - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/core/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/plugin-svgo/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@svgr/webpack": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", - "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@telegraf/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", - "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", - "license": "MIT" - }, - "node_modules/@thi.ng/bitstream": { - "version": "2.4.41", - "resolved": "https://registry.npmjs.org/@thi.ng/bitstream/-/bitstream-2.4.41.tgz", - "integrity": "sha512-treRzw3+7I1YCuilFtznwT3SGtceS9spUXhyBqeuKNTm4nIfMuvg4fNqx4GgpuS6cGPQNPMUJm0OyzKnSe2Emw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/thi.ng" - } - ], - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@thi.ng/errors": "^2.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@thi.ng/errors": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@thi.ng/errors/-/errors-2.6.3.tgz", - "integrity": "sha512-owkOOKHf7MrAPN2jNpKWDdY/vjtPFiJf6oxZ3jkkhV6ICTu2iY1fXIR2wQ7kVEeybdtb0w24k2PtrU43OYCWdg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/postspectacular" - }, - { - "type": "patreon", - "url": "https://patreon.com/thing_umbrella" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/thi.ng" - } - ], - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/inflate/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/@tokenizer/inflate/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/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "license": "MIT", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/dom-mediacapture-transform": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.11.tgz", - "integrity": "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==", - "license": "MIT", - "dependencies": { - "@types/dom-webcodecs": "*" - } - }, - "node_modules/@types/dom-webcodecs": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", - "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" + "dependencies": { + "@types/estree": "*" } }, "node_modules/@types/express": { @@ -13211,9 +10382,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", - "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "version": "4.19.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", "dev": true, "license": "MIT", "dependencies": { @@ -13229,17 +10400,10 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, - "node_modules/@types/gtag.js": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.20.tgz", - "integrity": "sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -13354,9 +10518,9 @@ "license": "MIT" }, "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", "dev": true, "license": "MIT" }, @@ -13374,12 +10538,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-fetch": { @@ -13399,17 +10563,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "dev": true, "license": "MIT" }, @@ -13421,13 +10578,12 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, @@ -13612,47 +10768,107 @@ "typescript": "*" } }, - "node_modules/@typescript/vfs/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" + }, + "node_modules/@unhead/bundler": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@unhead/bundler/-/bundler-3.2.3.tgz", + "integrity": "sha512-fL1LRdTyPYe9dzenElL96yaFW0LcXTUl1RSa5a2ni0n/GxMtvioPwz9y/fD4JnbVG//Kwn3YCv1jfUmlhcoW4g==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@vitejs/devtools-kit": "^0.4.1", + "magic-string": "^1.0.0", + "oxc-parser": "^0.140.0", + "oxc-walker": "^1.0.0", + "ufo": "^1.6.4", + "unplugin": "^3.3.0" }, - "engines": { - "node": ">=6.0" + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "@unhead/cli": "^3.2.3", + "esbuild": ">=0.17.0", + "lightningcss": ">=1.20.0", + "rolldown": ">=1.0.0-beta.0", + "unhead": "^3.2.3", + "vite": ">=6.4.2", + "webpack": ">=5.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "@unhead/cli": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { "optional": true } } }, - "node_modules/@typescript/vfs/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/@unhead/bundler/node_modules/magic-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.0.0.tgz", + "integrity": "sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" + "node_modules/@unhead/vue": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-3.2.3.tgz", + "integrity": "sha512-u1qi/0tDyytDSAex8+JMgeDMDlBh3RuzFmTaPOEmbAIx4BG9ej/18ChfM962DC0G7kKhOiuqiSpCfWR5S3XKGg==", + "license": "MIT", + "dependencies": { + "@unhead/bundler": "3.2.3", + "hookable": "^6.1.1", + "unhead": "3.2.3", + "unplugin": "^3.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vite": ">=6.4.2", + "vue": ">=3.5.18", + "webpack": ">=5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } }, "node_modules/@unocss/cli": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/cli/-/cli-66.7.2.tgz", - "integrity": "sha512-50vBptZyiyYzm5CBNSVs1WYIFX+7IKYFwLNrm6pVCOjHfrBmmpfvyCznPMzUcGEFKvP2VsyB2hf3k57GBCSS9Q==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/cli/-/cli-66.7.5.tgz", + "integrity": "sha512-fgWkECRn2LGVo9sEpmjk/KJ3NSKUDitaZCNrJcEYM93DG+ELriTHcL+VWBlF4rcZf9fdGlq1nKbbRHUZirFCHQ==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "@unocss/config": "66.7.2", - "@unocss/core": "66.7.2", - "@unocss/preset-wind3": "66.7.2", - "@unocss/preset-wind4": "66.7.2", - "@unocss/transformer-directives": "66.7.2", + "@unocss/config": "66.7.5", + "@unocss/core": "66.7.5", + "@unocss/preset-wind3": "66.7.5", + "@unocss/preset-wind4": "66.7.5", + "@unocss/transformer-directives": "66.7.5", "cac": "^7.0.0", "chokidar": "^5.0.0", "colorette": "^2.0.20", @@ -13660,7 +10876,7 @@ "magic-string": "^0.30.21", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", - "tinyglobby": "^0.2.16", + "tinyglobby": "^0.2.17", "unplugin-utils": "^0.3.1" }, "bin": { @@ -13699,12 +10915,12 @@ } }, "node_modules/@unocss/config": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/config/-/config-66.7.2.tgz", - "integrity": "sha512-m8LZUZOFHBesViFOnC1MzMMQ1ovYbZ/F2ntkKSIWzLO/VvEYo2/HK8qhBhtI/FyL27+gvePL4sZ6a5ZChyl0Ug==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/config/-/config-66.7.5.tgz", + "integrity": "sha512-dkPl9glhEahJ+Xoja5ZseKKnH+vZaeaKQzg8b0otcKcPPNrHQgu1nu3QgfeOjnXOGrjZIotHwUeVtt4ZA2Skgg==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", + "@unocss/core": "66.7.5", "colorette": "^2.0.20", "consola": "^3.4.2", "unconfig": "^7.5.0" @@ -13714,85 +10930,71 @@ } }, "node_modules/@unocss/core": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/core/-/core-66.7.2.tgz", - "integrity": "sha512-NNnhm9IVPEZ34drwztREP+mq1rio0L4Tp0u247qBKxJJWYec1+I+FTRsw7EvtukZKvr56YAxFA1qbBV+LjyV+Q==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/core/-/core-66.7.5.tgz", + "integrity": "sha512-UdJb8MiMywcau8QrWEVgUAz0kvoFHyR+sACwYCgmBh/BpKJGyR/zw/Ys3wvysbm0f+i/20VGBex/QQxYVlzdyQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/extractor-arbitrary-variants": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-66.7.2.tgz", - "integrity": "sha512-1R+ntws4zhi9gCsyovYeNCiAYGSceN6Rsy/4kyaw3npr1UBWhBJdQZtacxvqOssPfbbqq2vpatcuQ4rfZZYWFQ==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-66.7.5.tgz", + "integrity": "sha512-5zOkbnLIJc8E749qNjLXfPHl3FdHloop12h706/ojr6idK4ix0KLm43R//uLnphixCehdHJRuHnavnM4C/kQbA==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2" + "@unocss/core": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/extractor-mdc": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/extractor-mdc/-/extractor-mdc-66.7.2.tgz", - "integrity": "sha512-vc96a99Gns8IXS5D/u4+UWKB/W55uy/2Y5UeQZXcd8bV9muvwZWIPbjrpJe4a2VGX6DiHVYobMqR6wmjQdpLvg==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/extractor-mdc/-/extractor-mdc-66.7.5.tgz", + "integrity": "sha512-gwT/ahCTn4hYPu5GxZPiwRG1271IZw23a9mnvzbZoejGm6PoSbdfrkN0tc71CCoNmzC9G5/8f8tXtBeTLW8nhA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/inspector": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/inspector/-/inspector-66.7.2.tgz", - "integrity": "sha512-fvZ8w9dTnu61ZwbXjVMQopxxrQOnFOBN2I6KVPJtoUSMsatrpEYyJHDA9pfLes1a3C4eEJZaSADURHKkON09CA==", - "license": "MIT", - "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/rule-utils": "66.7.2", - "colorette": "^2.0.20", - "gzip-size": "^6.0.0", - "sirv": "^3.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@unocss/inspector/node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/inspector/-/inspector-66.7.5.tgz", + "integrity": "sha512-WmTJMnj8bmRxvw/wGeShmL2eEHr2/L0BdGTXSxhfzwcO8y5XZEbBmVciLcM7pqaTXQ6JY4+KnITrDUf1urjZxQ==", + "license": "MIT", + "dependencies": { + "@unocss/core": "66.7.5", + "@unocss/rule-utils": "66.7.5", + "colorette": "^2.0.20", + "gzip-size": "^6.0.0", + "sirv": "^3.0.2" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-attributify": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-attributify/-/preset-attributify-66.7.2.tgz", - "integrity": "sha512-JnnoRUgOL4O565+jNi8BfzTQDElEny1reaMhrdYQR4P4I7cfdRV89R5DsmND0H2mGtwJjP/gL4cWd1FSX9ShrA==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-attributify/-/preset-attributify-66.7.5.tgz", + "integrity": "sha512-1Oi1Cp81pqYJwG3h4+OlP5A0FKyaa07MFBXaXc4Yr3faiTbsI8SKj2TqnZCb+NQvBsEqslEd+jKXGZ3lKzCVOQ==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2" + "@unocss/core": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-icons": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-icons/-/preset-icons-66.7.2.tgz", - "integrity": "sha512-C05oM7j8jFuxjbPaRFUbcwxHPXvBtmJOhaE2M3YstVR/L9IBsQ6Ts/PT1vxVAVDmX19MudRRTnQ0x5XzUM3P7A==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-icons/-/preset-icons-66.7.5.tgz", + "integrity": "sha512-ZsxadWnGGtHdANTikuMnjkQaT1qwUFJHZBF4fzVsPMnUiiKGs8rzXukwM9w3Gi9ontM7nbG2FYi9clWETSlpFg==", "license": "MIT", "dependencies": { "@iconify/utils": "^3.1.3", - "@unocss/core": "66.7.2", + "@unocss/core": "66.7.5", "ofetch": "^1.5.1" }, "funding": { @@ -13800,61 +11002,61 @@ } }, "node_modules/@unocss/preset-mini": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-mini/-/preset-mini-66.7.2.tgz", - "integrity": "sha512-2DLS20vj+eoZI/r7U8eTxd+HTfMYamhx03mJyeadNu+efVJZrfEEMwjILgFywVAthYINmdeB4sYpc8Qfef4Vww==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-mini/-/preset-mini-66.7.5.tgz", + "integrity": "sha512-o37TSl4ecT0dKu+3/TYuTYht75h82SEwDNl476m9ve0KW4Pv1O2tT/9TWd7N5cutEpySr8/YtjMwtWBD+drxBg==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/extractor-arbitrary-variants": "66.7.2", - "@unocss/rule-utils": "66.7.2" + "@unocss/core": "66.7.5", + "@unocss/extractor-arbitrary-variants": "66.7.5", + "@unocss/rule-utils": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-tagify": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-tagify/-/preset-tagify-66.7.2.tgz", - "integrity": "sha512-46V3ibNqKEyeSNnplsOKiYiBdNZ348JgjhnGDWHigVYIfZkEqn6WJdGAX9tVZpjI/rS9px8jQA0lm6ubKoc8iQ==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-tagify/-/preset-tagify-66.7.5.tgz", + "integrity": "sha512-/8YB1tXVi+WmNKPhsCdtO1xnQKEkshSnWDp6AbvVP3f6qOF7adlMYpAt3kpWCoVUBsfOQ06ZQlnfricMjObyoQ==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2" + "@unocss/core": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-typography": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-typography/-/preset-typography-66.7.2.tgz", - "integrity": "sha512-6nTRoZiHTkDV87omRlEn8RZhakMYrIJtzazfj0rdF8msvjM1LnTT6K6qDlmxqb6NBIakljNb0bryubr6bWzK9A==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-typography/-/preset-typography-66.7.5.tgz", + "integrity": "sha512-2dxC8LT9KYa29UZT4muDFl7DbIXvKktTfeSMbUGMdZKbHcuMtKSP2U52wti1PL5I9duqp4v+8G33EeVutGTUaQ==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/rule-utils": "66.7.2" + "@unocss/core": "66.7.5", + "@unocss/rule-utils": "66.7.5" } }, "node_modules/@unocss/preset-uno": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-uno/-/preset-uno-66.7.2.tgz", - "integrity": "sha512-XiSGtnh04sGHCRUnlxhePBhRF8zfOQlCfYkKByQcp/pvhmFMIWwzVL68R5LwxBmBwBVY4hfQAIx0Sz/FbA3Ojg==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-uno/-/preset-uno-66.7.5.tgz", + "integrity": "sha512-zaUlYgNngbt50fZA/LbtsEnmPKojPqeiXCyUUiKQMv2uJQhncR32xhLZXVQ+TJD7hlfZ+6FAqlco1NIiAcub9w==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/preset-wind3": "66.7.2" + "@unocss/core": "66.7.5", + "@unocss/preset-wind3": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-web-fonts": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-web-fonts/-/preset-web-fonts-66.7.2.tgz", - "integrity": "sha512-Tx6YJWxD29NoG6t8hpbnectdL8KkBVEzEYwBcJlEa200O6/KXNpGJ8tk4l5+EK1dwTxWkUqsG+60fzXzTpe5Gg==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-web-fonts/-/preset-web-fonts-66.7.5.tgz", + "integrity": "sha512-OLLTK7kswdSu51qxEm6O+AehecgCfS08Ivqo+280lKzFW7V1jXr5okeJ1ty+85oHCprJqzcD9iG1khxNRLomcA==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", + "@unocss/core": "66.7.5", "ofetch": "^1.5.1" }, "funding": { @@ -13862,62 +11064,62 @@ } }, "node_modules/@unocss/preset-wind": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-wind/-/preset-wind-66.7.2.tgz", - "integrity": "sha512-porNxph4xfr67e0LpFis813rKk9+psUJMw0nPL4sZoHovhmR/hA5XlWb6fLCl7t4Lls20I/n8F8KKgkqkxnk8Q==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-wind/-/preset-wind-66.7.5.tgz", + "integrity": "sha512-LVKgGr0A9Lc7F85xGXrrb71DDXMlHGA8bcj/Kht8BCsuRdBZadNbLlnv5qnSJiHYhi3/blzcgVoaeRor6L9yNA==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/preset-wind3": "66.7.2" + "@unocss/core": "66.7.5", + "@unocss/preset-wind3": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-wind3": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-wind3/-/preset-wind3-66.7.2.tgz", - "integrity": "sha512-3WUmNZ3ibNotel6PAm1AgdK8BP2RqThRvEYU+svZgxsCX8E/RtVM68BFPOwzsEtuMD/R3Up6rHXqZsJvUQsg+A==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-wind3/-/preset-wind3-66.7.5.tgz", + "integrity": "sha512-atFe/7Qein+oMdyZs0hEo+3EjV99Av2LVxDbzpCungxIfbS3TnQt70EIuHXMPNCVWLYwrMV+/P1n9Ntb0E3mow==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/preset-mini": "66.7.2", - "@unocss/rule-utils": "66.7.2" + "@unocss/core": "66.7.5", + "@unocss/preset-mini": "66.7.5", + "@unocss/rule-utils": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/preset-wind4": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/preset-wind4/-/preset-wind4-66.7.2.tgz", - "integrity": "sha512-yP1Np15QKm+zh945lBmpNC2FnD4oyd0eq9qA6j8r15uvhz7AF98t9dGqqzV5WrjX+IZpwg08nP3son1IjAerLw==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/preset-wind4/-/preset-wind4-66.7.5.tgz", + "integrity": "sha512-n3jIvQv8x1jXpmWfvNFBIqHNARki4mKZXfxAA31gekpXsRRTg16u1+yC1+PBBJgoEDFEnnmKnG7EZP88S8LVXA==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/extractor-arbitrary-variants": "66.7.2", - "@unocss/rule-utils": "66.7.2" + "@unocss/core": "66.7.5", + "@unocss/extractor-arbitrary-variants": "66.7.5", + "@unocss/rule-utils": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/reset": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/reset/-/reset-66.7.2.tgz", - "integrity": "sha512-dKRbpBicfchatvqK2wqfX8gtNTCf+2pFdXBNDC0Cl2PZ8QCVMtCaHTOTSyruM0Pw7aqST4SlPkF5f8x7aZZL0w==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/reset/-/reset-66.7.5.tgz", + "integrity": "sha512-z3wbt2cuQPjtT6w5xzRYZYFIIlUPzhVKz8yIcE9fvu7BgR3hO7uVsg/5mzDoGwQ96Ya3Sk68rpmI/gLYl4bJag==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@unocss/rule-utils": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/rule-utils/-/rule-utils-66.7.2.tgz", - "integrity": "sha512-EGi2m9I87hluz2zgjVpXM4PWFn996RInNcx4PGF6Qw9Z0W78ROXEto0SM1IltpJ4R7+at4EhssU0IbHiT0snEw==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/rule-utils/-/rule-utils-66.7.5.tgz", + "integrity": "sha512-/AKHBRF6ZOexE3EDDv8ZEYh40P5e2Eha41IYUbE1wjigW7++ssmvimSTdufJzZvU5DGP++E3B3WEsFPprZMjAg==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", + "@unocss/core": "66.7.5", "magic-string": "^0.30.21" }, "funding": { @@ -13925,12 +11127,12 @@ } }, "node_modules/@unocss/transformer-attributify-jsx": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/transformer-attributify-jsx/-/transformer-attributify-jsx-66.7.2.tgz", - "integrity": "sha512-lb5y4lwHCjZm+9L3k6c/fq9O75+mQcxBp2Dq+a3DS+vOQpPce+hOyLFFkiHVRNKp9chEHS9gnq58SBVxJbhR2A==", + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/transformer-attributify-jsx/-/transformer-attributify-jsx-66.7.5.tgz", + "integrity": "sha512-r8qDwNSt0eGSwWws3xsuIHb3PZYR2embFdYoE67hD/xlYgLjX1uPy/HUlGCSSh4uLc4vVYjurr0Oq5xF/B8YiA==", "license": "MIT", "dependencies": { - "@unocss/core": "66.7.2", + "@unocss/core": "66.7.5", "oxc-parser": "^0.131.0", "oxc-walker": "^0.7.0" }, @@ -13938,1255 +11140,1276 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@unocss/transformer-compile-class": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/transformer-compile-class/-/transformer-compile-class-66.7.2.tgz", - "integrity": "sha512-/vdgxgUI9vp7NOGfuOCY44/Ja2YbR0m+sWCGHq8K8/53a3Dk+4AvXPePv/EI/Oo6z8bhSGZHCSes8pBEY8Mr8A==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "license": "MIT", + "optional": true, "dependencies": { - "@unocss/core": "66.7.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@unocss/transformer-directives": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/transformer-directives/-/transformer-directives-66.7.2.tgz", - "integrity": "sha512-9xr5Tiy+urutRBcyKJUAOOpW3LSuSM59sKowdTStzZdBUMs/L9cmjfsjNFt8rm5tptUR25wGZ8xLR5hhVDAiBQ==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", + "optional": true, "dependencies": { - "@unocss/core": "66.7.2", - "@unocss/rule-utils": "66.7.2", - "css-tree": "^3.2.1" + "tslib": "^2.4.0" } }, - "node_modules/@unocss/transformer-directives/node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "license": "MIT", + "optional": true, "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@unocss/transformer-directives/node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "license": "CC0-1.0" - }, - "node_modules/@unocss/transformer-variant-group": { - "version": "66.7.2", - "resolved": "https://registry.npmjs.org/@unocss/transformer-variant-group/-/transformer-variant-group-66.7.2.tgz", - "integrity": "sha512-CO0CoYRn96wLm+cIICuNrv96cfzEkBHc/OmTYcHzlheyZRfGWPWlPpazMr2rlm5bve986akAxe2HSBqdaRf04w==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "license": "MIT", + "optional": true, "dependencies": { - "@unocss/core": "66.7.2" + "@tybys/wasm-util": "^0.10.3" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@upsetjs/venn.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", - "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.131.0.tgz", + "integrity": "sha512-t2xicr9pfzkSRYx5aPqZqlLaayIwJTqgQ81Jor31Xep2nGyL2Aq3d0K5wOfeR7VevaSdxaS9dzSQP9xDwn8fDg==", + "cpu": [ + "arm" + ], "license": "MIT", - "optionalDependencies": { - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@valibot/to-json-schema": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.7.1.tgz", - "integrity": "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.131.0.tgz", + "integrity": "sha512-nlGIod6gw75x1aEDgLS+srj+JRGY0HHm9MI9YgzE/B64l6d6+H3MSP9NOgp0+HTg8tp4vV9rVfgQGgd+TfVZcA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "peerDependencies": { - "valibot": "^1.4.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", - "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.131.0.tgz", + "integrity": "sha512-jukuV6xe5RbQKFo7QD34NDCLDZp4PSOm8rmckhNdH/60ymG5zXbDzGBEyc+nTkuLQNama2aSGCt+CPfpjNTqyw==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@volar/language-core": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.131.0.tgz", + "integrity": "sha512-g3JOo4khe9rslHm5WYaVDWb0HS/M1MLR3I9S8560MkKIcC96VQY00QjOlsuRyfSj/JDXj8i9T7ryPO2RidiXVg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.28" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@volar/source-map": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", - "license": "MIT" + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.131.0.tgz", + "integrity": "sha512-1hziITDTxjMePnX+dR9ocVT+EuZkQ8wm4FPAbmbEiKG+Phbo73J1ZnPAA6Y/aGsWF3McOFnQuZIktAFwalkfJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@vue-macros/common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", - "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.131.0.tgz", + "integrity": "sha512-9uRxfXwyKG9+MwmGQBo2ncPNwZH5HTmCETFM2WiuDBNDCW4NC5ttSQkwCAMrTAWgwMzVBH1CP8pM0v7nebCWXQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@vue/compiler-sfc": "^3.5.22", - "ast-kit": "^2.1.2", - "local-pkg": "^1.1.2", - "magic-string-ast": "^1.0.2", - "unplugin-utils": "^0.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/vue-macros" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.2.25" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-2.0.1.tgz", - "integrity": "sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==", - "license": "MIT" + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.131.0.tgz", + "integrity": "sha512-mgbLvzRShXOLBdWGInf08Af4q+pfj1xD8hSgLClDZ9of/BXkB6+LIhTH7fihiDUipqB3yoSkKBWaZ3Ejlf5Yag==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@vue/babel-plugin-jsx": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-2.0.1.tgz", - "integrity": "sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.131.0.tgz", + "integrity": "sha512-OPT8++4aN6j2GJ8+3IZHS/byXoZP4aSBn+FoG6rgBJ2fKwPKXWF3MqrFMNW7NKHM28FLY579xYLxJSfgobEqPA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@vue/babel-helper-vue-transform-on": "2.0.1", - "@vue/babel-plugin-resolve-type": "2.0.1", - "@vue/shared": "^3.5.22" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/babel-plugin-resolve-type": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-2.0.1.tgz", - "integrity": "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.131.0.tgz", + "integrity": "sha512-vtPiwmfVTAXzaxDKsOXG+LwgRAA7WEnaeHzhS5z0GE89gAK18KSXnly7Z6saXXq6L3dVMyK44uoTI03zKxrpmw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/parser": "^7.28.4", - "@vue/compiler-sfc": "^3.5.22" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/compiler-core": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz", - "integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.131.0.tgz", + "integrity": "sha512-8AW8L7w5cGHSdZPcyZX2yR0+GUODsT15rbRjfdD54rv6DMbtuEB19ysLOpKJlRGfH6UNYNpCHaU1uJWgTWf1/w==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.38", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.131.0.tgz", + "integrity": "sha512-vvpjkjEOUsPcsYf8evE4MO3aGx9+3wodXEBOicGNnOwTuAik8eBONNkgSdhkGsAblQmfVHJyanRnpxglddTXIA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz", - "integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.131.0.tgz", + "integrity": "sha512-AqmcNC3fClXX+fxQ6VGEN1667xVFiRBkY0CZmDMSiaeFUsv1+UkBPYYi48IUKcA9/ivvoKNRzQl2I4//kT9F/w==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.38", - "@vue/shared": "3.5.38" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz", - "integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.131.0.tgz", + "integrity": "sha512-7d3jOMKy7RSQCcDLIci+ySll2FgsOMl/GiRux4q2JNv0zg4EdhFISa9idvrdN/HEUIQQJNg6dmveUeJl2YErGA==", + "cpu": [ + "s390x" + ], "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.38", - "@vue/compiler-dom": "3.5.38", - "@vue/compiler-ssr": "3.5.38", - "@vue/shared": "3.5.38", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.15", - "source-map-js": "^1.2.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.131.0.tgz", + "integrity": "sha512-JHK/h95qVqVQ+ITER837kcTdwBDFpFaNnOTYGCP0zdUSX/mLKC7tXOoyrTb6vG7iRPwGlcgBil3v2IjYw1FqJA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz", - "integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.131.0.tgz", + "integrity": "sha512-b2BO82O8azXAyf7EUgOPKu145nWypbNyk07HbU09fkzhm9lEA5oPvaN/M8Nlo7tOErVTa2WOgS4QbOnxAPXdDQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.38", - "@vue/shared": "3.5.38" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/devtools-api": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.3.tgz", - "integrity": "sha512-73NMCvxXh8Hyozc/jiwqTFWVcCMyi11U1zmrq4DoukQJnuo8JHt6FsNu4HdeUDa8SpIp5vb7Q22GWgIq0efsXg==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.131.0.tgz", + "integrity": "sha512-GHO9glZaX7LkX/OGfluEPf1yjg+ehiFbUdowbX6uNWOQhmwKWU4m4+nZ9FJkrHNKuxyI1KKertMdGjVKCApKWA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.1.3" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/devtools-kit": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.3.tgz", - "integrity": "sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.131.0.tgz", + "integrity": "sha512-3SkikPaEFoih1N83qLVEDLRLeY4nYsf6JT9SnWiMCQ5lGQdKup6bEuKCqkRiG9dD1IIaFeYz9RjlciPmYoFIWA==", + "cpu": [ + "wasm32" + ], "license": "MIT", + "optional": true, "dependencies": { - "@vue/devtools-shared": "^8.1.3", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "perfect-debounce": "^2.0.0" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/devtools-kit/node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.131.0.tgz", + "integrity": "sha512-Os5bEhryeA2jkH+ZrnZyAC1EP5gs+X4YB1Fjqml7UPD5kU7ecsK1MPEVMfCrdt/GDNpDbavYXiOXOdyJ5b3OPw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/devtools-kit/node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/@vue/devtools-shared": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.3.tgz", - "integrity": "sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA==", - "license": "MIT" - }, - "node_modules/@vue/language-core": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.5.tgz", - "integrity": "sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.131.0.tgz", + "integrity": "sha512-m+jNz9EuF0NXoiptc6B9h5yompZQVW/a5MJeOu5zojfH5yWk82tvF2ccrHkfhgtrS9h9DD5l1Qv8dWlfY7Nz8g==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "@vue/compiler-dom": "^3.5.0", - "@vue/shared": "^3.5.0", - "alien-signals": "^3.2.0", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1", - "picomatch": "^4.0.4" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/language-core/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.131.0.tgz", + "integrity": "sha512-o14Hk8dAyiEUMFEWEgmAwFZvBt1RzAYLM3xeQ+5315JXgVYhoemivgYcbYVRbsFkS71ShMGlAFE0kPnr460rww==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vue/reactivity": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", - "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/@oxc-project/types": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.131.0.tgz", + "integrity": "sha512-PgnWDfV0h+b16XNKbXU7Daib/BFSt/J2mEzfYIBu6JB/wNdlU+kVYXCkGA1A9fWkTbOgbjh4e6NhPeQOYvFhEA==", "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.38" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@vue/runtime-core": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz", - "integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/magic-regexp": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", + "integrity": "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.38", - "@vue/shared": "3.5.38" + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12", + "mlly": "^1.7.2", + "regexp-tree": "^0.1.27", + "type-level-regexp": "~0.1.17", + "ufo": "^1.5.4", + "unplugin": "^2.0.0" } }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz", - "integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/oxc-parser": { + "version": "0.131.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.131.0.tgz", + "integrity": "sha512-SJ3/7ZPbgie8dr5Z9BI/M51zZbpXba+hRSG0MDzVwMW5CRQg2fjYE0jHGlLX4eeiibGgC/mzoDFKSDHwVZEHRQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.38", - "@vue/runtime-core": "3.5.38", - "@vue/shared": "3.5.38", - "csstype": "^3.2.3" + "@oxc-project/types": "^0.131.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.131.0", + "@oxc-parser/binding-android-arm64": "0.131.0", + "@oxc-parser/binding-darwin-arm64": "0.131.0", + "@oxc-parser/binding-darwin-x64": "0.131.0", + "@oxc-parser/binding-freebsd-x64": "0.131.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.131.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.131.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.131.0", + "@oxc-parser/binding-linux-arm64-musl": "0.131.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.131.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.131.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.131.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.131.0", + "@oxc-parser/binding-linux-x64-gnu": "0.131.0", + "@oxc-parser/binding-linux-x64-musl": "0.131.0", + "@oxc-parser/binding-openharmony-arm64": "0.131.0", + "@oxc-parser/binding-wasm32-wasi": "0.131.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.131.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.131.0", + "@oxc-parser/binding-win32-x64-msvc": "0.131.0" } }, - "node_modules/@vue/server-renderer": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz", - "integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/oxc-walker": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/oxc-walker/-/oxc-walker-0.7.0.tgz", + "integrity": "sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.38", - "@vue/shared": "3.5.38" + "magic-regexp": "^0.10.0" }, "peerDependencies": { - "vue": "3.5.38" + "oxc-parser": ">=0.98.0" } }, - "node_modules/@vue/shared": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", - "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", - "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "node_modules/@unocss/transformer-attributify-jsx/node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", "license": "MIT", "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.3.0", - "@vueuse/shared": "14.3.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" }, - "peerDependencies": { - "vue": "^3.5.0" + "engines": { + "node": ">=18.12.0" } }, - "node_modules/@vueuse/math": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/math/-/math-14.3.0.tgz", - "integrity": "sha512-pE+psi030akIORcw/4OMg2t+mc5mIjLh2EPMx0ZnSvOUOC+Kjsk3qWVDECMn5+vR2YhVW+h26Dk8h1ag70vRZA==", + "node_modules/@unocss/transformer-compile-class": { + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/transformer-compile-class/-/transformer-compile-class-66.7.5.tgz", + "integrity": "sha512-KuECgsGF7tGe808vIvKViEjI0UCUgYgneJfK+/TWBkjC7s8dLVaUtJzzcP9/r6OfGlgyn/x1YTdRqTaCENa7Xg==", "license": "MIT", "dependencies": { - "@vueuse/shared": "14.3.0" + "@unocss/core": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" } }, - "node_modules/@vueuse/metadata": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", - "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "node_modules/@unocss/transformer-directives": { + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/transformer-directives/-/transformer-directives-66.7.5.tgz", + "integrity": "sha512-VMJApXXOwlDubkW+cNMmOkfxLefFupJr+yU23gGYUB2NPsuVltc8H5CDM0gfVebpeL5CUW05evxzEAk2wsju+Q==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "dependencies": { + "@unocss/core": "66.7.5", + "@unocss/rule-utils": "66.7.5", + "css-tree": "^3.2.1" } }, - "node_modules/@vueuse/motion": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vueuse/motion/-/motion-3.0.3.tgz", - "integrity": "sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==", + "node_modules/@unocss/transformer-directives/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "license": "MIT", "dependencies": { - "@vueuse/core": "^13.0.0", - "@vueuse/shared": "^13.0.0", - "defu": "^6.1.4", - "framesync": "^6.1.2", - "popmotion": "^11.0.5", - "style-value-types": "^5.1.2" - }, - "optionalDependencies": { - "@nuxt/kit": "^3.13.0" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, - "peerDependencies": { - "vue": ">=3.0.0" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/@vueuse/motion/node_modules/@vueuse/core": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", - "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "node_modules/@unocss/transformer-directives/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/@unocss/transformer-variant-group": { + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/transformer-variant-group/-/transformer-variant-group-66.7.5.tgz", + "integrity": "sha512-iDmP3mM8J+IxuQf5Uh33zsi528xnGxTpKRJ0c+LCrqBTTkR2tZr4aCzNmJ0g29Js2yEOsyNXRRc8BNTuvgn0AA==", "license": "MIT", "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "13.9.0", - "@vueuse/shared": "13.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "@unocss/core": "66.7.5" }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@vueuse/motion/node_modules/@vueuse/metadata": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", - "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@vueuse/motion/node_modules/@vueuse/shared": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", - "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "node_modules/@unocss/vite": { + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/@unocss/vite/-/vite-66.7.5.tgz", + "integrity": "sha512-1z1TBCNJCR2WzjPtjzmCHr8rtzXHosMjLdsCNe6Mzsfwiw044gzzLVuiEZO9vIjkI50lvQ+gIOxOiVQG0hGAeA==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "@unocss/config": "66.7.5", + "@unocss/core": "66.7.5", + "@unocss/inspector": "66.7.5", + "chokidar": "^5.0.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3", + "tinyglobby": "^0.2.17", + "unplugin-utils": "^0.3.1" }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@vueuse/shared": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", - "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vue": "^3.5.0" + "vite": "^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0" } }, - "node_modules/@wasm-audio-decoders/common": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/common/-/common-9.0.7.tgz", - "integrity": "sha512-WRaUuWSKV7pkttBygml/a6dIEpatq2nnZGFIoPTc5yPLkxL6Wk4YaslPM98OPQvWacvNZ+Py9xROGDtrFBDzag==", + "node_modules/@unocss/vite/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@eshaz/web-worker": "1.2.2", - "simple-yenc": "^1.0.4" + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@wasm-audio-decoders/flac": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/flac/-/flac-0.2.10.tgz", - "integrity": "sha512-YfcyoD2rYRBa6ffawZKNi5qvV5HArJmNmuMVUPoutuZ2hhGi6WNSWIzgvbROGmPbFivLL764Am7xxJENWJDhjw==", + "node_modules/@unocss/vite/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@wasm-audio-decoders/common": "9.0.7", - "codec-parser": "2.5.0" + "engines": { + "node": ">= 20.19.0" }, "funding": { "type": "individual", - "url": "https://github.com/sponsors/eshaz" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@wasm-audio-decoders/ogg-vorbis": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/ogg-vorbis/-/ogg-vorbis-0.1.20.tgz", - "integrity": "sha512-zaQPasU5usRjUDXtXOHYED5tfkR4QMXd+EH3Nrz1+4+M5pCsdD+s9YxJqb0oqnTyRu/KUujOmu5Z/m/NT47vwg==", + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@wasm-audio-decoders/common": "9.0.7", - "codec-parser": "2.5.0" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/eshaz" + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" } }, - "node_modules/@wasm-audio-decoders/opus-ml": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/opus-ml/-/opus-ml-0.0.2.tgz", - "integrity": "sha512-58rWEqDGg+CKCyEeKm2KoxxSwTWtHh/NLTW9ObR4K8CGF6VwuuGudEI1CtniS/oSRmL1nJq/eh8MKARiluw4DQ==", + "node_modules/@valibot/to-json-schema": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.7.1.tgz", + "integrity": "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==", "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@wasm-audio-decoders/common": "9.0.7" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/eshaz" + "peerDependencies": { + "valibot": "^1.4.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "node_modules/@vitejs/devtools-kit": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@vitejs/devtools-kit/-/devtools-kit-0.4.3.tgz", + "integrity": "sha512-FjETJwshQQwa9DNeCYtjzfQhvye3e4XMAkDSVmdDicPj8dJM6lJapKTHvNU7SUoxgm8e5tc7OcFyQYaq1xDH3A==", "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + "@devframes/hub": "^0.7.9", + "@devframes/json-render": "^0.7.9", + "devframe": "^0.7.9", + "local-pkg": "^1.2.1", + "mlly": "^1.8.2", + "nostics": "^1.2.0", + "nypm": "^0.6.8" + }, + "peerDependencies": { + "vite": "*" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "node_modules/@vitejs/plugin-vue-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.6.tgz", + "integrity": "sha512-YXvi4as2clxt6DFw5+a0tTA97ntiQXm/raR8ofNj3aNwwdlVGTiG2gp7EvfZW17P50acL/9bP0ccF4XnqNmlgA==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" + "@babel/core": "^7.29.0", + "@babel/plugin-syntax-typescript": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7", + "@rolldown/pluginutils": "^1.0.1", + "@vue/babel-plugin-jsx": "^2.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.0.0" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "license": "MIT", "dependencies": { - "@xtuc/long": "4.2.2" + "@volar/source-map": "2.4.28" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", "license": "MIT" }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "node_modules/@vue-macros/common": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz", + "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-2.0.1.tgz", + "integrity": "sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==", + "license": "MIT" }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "node_modules/@vue/babel-plugin-jsx": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-2.0.1.tgz", + "integrity": "sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@vue/babel-helper-vue-transform-on": "2.0.1", + "@vue/babel-plugin-resolve-type": "2.0.1", + "@vue/shared": "^3.5.22" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-2.0.1.tgz", + "integrity": "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.4", + "@vue/compiler-sfc": "^3.5.22" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" }, - "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==", + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" } }, - "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==", + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "node_modules/@vue/devtools-api": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.5.tgz", + "integrity": "sha512-YJipMVAKe5wT5CWf5kTYCaNV7NMNjFVxJkIkJaJ4W/nCxEBzlZzrOsYKeCymdCrFZmBS/+wTWFoUs3Jf/Q6XSQ==", "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" + "dependencies": { + "@vue/devtools-kit": "^8.1.5" } }, - "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, + "node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.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, + "node_modules/@vue/devtools-kit/node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, + "node_modules/@vue/devtools-kit/node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.8.tgz", + "integrity": "sha512-ieGT8jJdhhy0mGzStZhsg/qPw5bQZJg5yF+3+XU6saf4sM7yo9ZXy3h+nCwrm2+b4qS/SypkNdR2jAF3uei9tA==", "license": "MIT", - "engines": { - "node": ">= 10.0.0" + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" } }, - "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==", + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", "license": "MIT", - "engines": { - "node": ">= 14" + "dependencies": { + "@vue/shared": "3.5.40" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", "license": "MIT", "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" } }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", "license": "MIT", "dependencies": { - "ajv": "^8.0.0" + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" }, - "peerDependencies": { - "ajv": "^8.0.0" + "funding": { + "url": "https://github.com/sponsors/antfu" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "peerDependencies": { + "vue": "^3.5.0" } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/@vueuse/math": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/math/-/math-14.3.0.tgz", + "integrity": "sha512-pE+psi030akIORcw/4OMg2t+mc5mIjLh2EPMx0ZnSvOUOC+Kjsk3qWVDECMn5+vR2YhVW+h26Dk8h1ag70vRZA==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "ajv": "^8.8.2" + "vue": "^3.5.0" } }, - "node_modules/algoliasearch": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.50.2.tgz", - "integrity": "sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw==", - "dev": true, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.16.2", - "@algolia/client-abtesting": "5.50.2", - "@algolia/client-analytics": "5.50.2", - "@algolia/client-common": "5.50.2", - "@algolia/client-insights": "5.50.2", - "@algolia/client-personalization": "5.50.2", - "@algolia/client-query-suggestions": "5.50.2", - "@algolia/client-search": "5.50.2", - "@algolia/ingestion": "1.50.2", - "@algolia/monitoring": "1.50.2", - "@algolia/recommend": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" - }, - "engines": { - "node": ">= 14.0.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/algoliasearch-helper": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.28.1.tgz", - "integrity": "sha512-6iXpbkkrAI5HFpCWXlNmIDSBuoN/U1XnEvb2yJAoWfqrZ+DrybI7MQ5P5mthFaprmocq+zbi6HxnR28xnZAYBw==", - "dev": true, + "node_modules/@vueuse/motion": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vueuse/motion/-/motion-3.0.3.tgz", + "integrity": "sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==", "license": "MIT", "dependencies": { - "@algolia/events": "^4.0.1" + "@vueuse/core": "^13.0.0", + "@vueuse/shared": "^13.0.0", + "defu": "^6.1.4", + "framesync": "^6.1.2", + "popmotion": "^11.0.5", + "style-value-types": "^5.1.2" + }, + "optionalDependencies": { + "@nuxt/kit": "^3.13.0" }, "peerDependencies": { - "algoliasearch": ">= 3.1 < 6" + "vue": ">=3.0.0" } }, - "node_modules/alien-signals": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", - "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", - "license": "MIT" - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "license": "ISC", + "node_modules/@vueuse/motion/node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, - "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==", + "node_modules/@vueuse/motion/node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "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==", + "node_modules/@vueuse/motion/node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, - "node_modules/ansis": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", - "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "license": "MIT" }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "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==", - "license": "Python-2.0" - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "license": "MIT" }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "license": "MIT" }, - "node_modules/asn1js": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", - "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.5", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=12.0.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "node_modules/ast-kit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", - "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "pathe": "^2.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, - "node_modules/ast-walker-scope": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", - "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.2", - "@babel/types": "^7.29.0", - "ast-kit": "^2.2.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "dev": true, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "bin": { - "astring": "bin/astring" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", "dependencies": { - "lodash": "^4.17.14" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/async-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" } }, - "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/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" }, - "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==", + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "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": ">=8.0.0" + "node": ">=6.5" } }, - "node_modules/audio-buffer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/audio-buffer/-/audio-buffer-5.0.0.tgz", - "integrity": "sha512-gsDyj1wwUp8u7NBB+eW6yhLb9ICf+0eBmDX8NGaAS00w8/fLqFdxUlL5Ge/U8kB64DlQhdonxYC59dXy1J7H/w==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/audio-decode": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/audio-decode/-/audio-decode-2.2.3.tgz", - "integrity": "sha512-Z0lHvMayR/Pad9+O9ddzaBJE0DrhZkQlStrC1RwcAHF3AhQAsdwKHeLGK8fYKyp2DDU6xHxzGb4CLMui12yVrg==", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@wasm-audio-decoders/flac": "^0.2.4", - "@wasm-audio-decoders/ogg-vorbis": "^0.1.15", - "audio-buffer": "^5.0.0", - "audio-type": "^2.2.1", - "mpg123-decoder": "^1.0.0", - "node-wav": "^0.0.2", - "ogg-opus-decoder": "^1.6.12", - "qoa-format": "^1.0.1" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/audio-type": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/audio-type/-/audio-type-2.2.1.tgz", - "integrity": "sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==", + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", - "optional": true, - "peer": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=14" + "node": ">=0.4.0" } }, - "node_modules/autocannon": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/autocannon/-/autocannon-7.15.0.tgz", - "integrity": "sha512-NaP2rQyA+tcubOJMFv2+oeW9jv2pq/t+LM6BL3cfJic0HEfscEcnWgAyU5YovE/oTHUzAgTliGdLPR+RQAWUbg==", - "dev": true, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "char-spinner": "^1.0.1", - "cli-table3": "^0.6.0", - "color-support": "^1.1.1", - "cross-argv": "^2.0.0", - "form-data": "^4.0.0", - "has-async-hooks": "^1.0.0", - "hdr-histogram-js": "^3.0.0", - "hdr-histogram-percentiles-obj": "^3.0.0", - "http-parser-js": "^0.5.2", - "hyperid": "^3.0.0", - "lodash.chunk": "^4.2.0", - "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", - "manage-path": "^2.0.0", - "on-net-listen": "^1.1.1", - "pretty-bytes": "^5.4.1", - "progress": "^2.0.3", - "reinterval": "^1.1.0", - "retimer": "^3.0.0", - "semver": "^7.3.2", - "subarg": "^1.0.0", - "timestring": "^6.0.0" + "engines": { + "node": ">=10.13.0" }, - "bin": { - "autocannon": "autocannon.js" + "peerDependencies": { + "acorn": "^8.14.0" } }, - "node_modules/autoprefixer": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", - "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "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, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.2", - "caniuse-lite": "^1.0.30001787", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" + "acorn": "^8.11.0" }, "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=0.4.0" } }, - "node_modules/axios": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", - "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "node_modules/address": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/address/-/address-2.0.3.tgz", + "integrity": "sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==", + "dev": true, "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" + "engines": { + "node": ">= 16.0.0" } }, - "node_modules/axios/node_modules/agent-base": { + "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", @@ -15198,1067 +12421,995 @@ "node": ">= 6.0.0" } }, - "node_modules/axios/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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": { - "ms": "^2.1.3" + "humanize-ms": "^1.2.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 8.0.0" } }, - "node_modules/axios/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/axios/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/axios/node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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", + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, "peerDependencies": { - "react-native-b4a": "*" + "ajv": "^8.0.0" }, "peerDependenciesMeta": { - "react-native-b4a": { + "ajv": { "optional": true } } }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dev": true, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" + "fast-deep-equal": "^3.1.3" }, "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" + "ajv": "^8.8.2" } }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "node_modules/algoliasearch": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.56.0.tgz", + "integrity": "sha512-PrqppUmhT4ENdas2pH9caE7efUcxy6EcSFhWzosiVuQBzu2tQ5yLTI6jwomT/1cuBnivzGfxiJCqDNN9FRRh+Q==", "dev": true, "license": "MIT", "dependencies": { - "object.assign": "^4.1.0" + "@algolia/abtesting": "1.22.0", + "@algolia/client-abtesting": "5.56.0", + "@algolia/client-analytics": "5.56.0", + "@algolia/client-common": "5.56.0", + "@algolia/client-insights": "5.56.0", + "@algolia/client-personalization": "5.56.0", + "@algolia/client-query-suggestions": "5.56.0", + "@algolia/client-search": "5.56.0", + "@algolia/ingestion": "1.56.0", + "@algolia/monitoring": "1.56.0", + "@algolia/recommend": "5.56.0", + "@algolia/requester-browser-xhr": "5.56.0", + "@algolia/requester-fetch": "5.56.0", + "@algolia/requester-node-http": "5.56.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "node_modules/algoliasearch-helper": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.29.2.tgz", + "integrity": "sha512-SaV+rZM3drExb0punEYYjT+sNcH74YFwN8ocjya7IDOyQvKWeQpEaSMVG3+IGTVos+feuatj7ljQ4BXlXdUp3w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" + "@algolia/events": "^4.0.1" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "algoliasearch": ">= 3.1 < 6" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "license": "MIT" }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "string-width": "^4.1.0" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } + "license": "MIT" }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "node_modules/ansi-align/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", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/baileys": { - "version": "6.7.23", - "resolved": "https://registry.npmjs.org/baileys/-/baileys-6.7.23.tgz", - "integrity": "sha512-y/qGIdZyIdVBD76uHX9YnYcqEywJSmPgIYG40YcLBoo1uuoQS+MU2IpfP6ZFodv5girKbhvp4+ID9J59QtQN3g==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@cacheable/node-cache": "^1.4.0", - "@hapi/boom": "^9.1.3", - "async-mutex": "^0.5.0", - "axios": "^1.6.0", - "libsignal": "git+https://github.com/whiskeysockets/libsignal-node.git", - "music-metadata": "^11.7.0", - "pino": "^9.6", - "protobufjs": "^7.2.4", - "ws": "^8.13.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "audio-decode": "^2.1.3", - "jimp": "^1.6.0", - "link-preview-js": "^3.0.0", - "sharp": "*" - }, - "peerDependenciesMeta": { - "audio-decode": { - "optional": true - }, - "jimp": { - "optional": true - }, - "link-preview-js": { - "optional": true - } - } - }, - "node_modules/baileys/node_modules/libsignal": { - "name": "@whiskeysockets/libsignal-node", - "version": "2.0.1", - "resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67", - "license": "GPL-3.0", "dependencies": { - "curve25519-js": "^0.0.4", - "protobufjs": "6.8.8" - } - }, - "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/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": "*" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "node_modules/bare-fs": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", - "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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/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": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" + "color-convert": "^2.0.1" }, "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" + "node": ">=8" }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/bare-os": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.7.1.tgz", - "integrity": "sha512-ebvMaS5BgZKmJlvuWh14dg9rbUI84QeV3WlWn6Ph6lFI8jJoh7ADtVTyD2c93euwbe+zgi0DVrl4YmqXeM9aIA==", - "license": "Apache-2.0", + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "license": "ISC", "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "dependencies": { - "bare-os": "^3.0.1" + "node": ">=14" } }, - "node_modules/bare-stream": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", - "license": "Apache-2.0", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { - "streamx": "^2.21.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } + "engines": { + "node": ">= 8" } }, - "node_modules/bare-url": { + "node_modules/anymatch/node_modules/picomatch": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "license": "Apache-2.0", - "dependencies": { - "bare-path": "^3.0.0" + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "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" - } - ], + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "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==", + "license": "Python-2.0" + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", "license": "MIT", "engines": { - "node": "^4.5.0 || >= 5.9" + "node": ">=0.10.0" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.17", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz", - "integrity": "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=8" } }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "license": "MIT", + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" }, "engines": { - "node": ">= 18" + "node": ">=12.0.0" } }, - "node_modules/better-sqlite3": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", - "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", - "hasInstallScript": true, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", "license": "MIT", "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - } - }, - "node_modules/better-sqlite3-session-store": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/better-sqlite3-session-store/-/better-sqlite3-session-store-0.1.0.tgz", - "integrity": "sha512-O4EO5jOGTEa/c1DbZpP3C7VTDLSWe5lrOu1S/j86ipdGZxrSb8bSUVuRgWCgl/SCgEGmyeEqvlMY9HtyOSMOWA==", - "license": "GPL-3.0-only", - "dependencies": { - "date-fns": "2.16.1" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "license": "MIT", + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, "engines": { - "node": "*" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" } }, - "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==", + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, "engines": { - "node": ">=8" + "node": ">=20.19.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "dev": true, "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" + "bin": { + "astring": "bin/astring" } }, - "node_modules/birpc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", - "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "dependencies": { + "lodash": "^4.17.14" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "tslib": "^2.4.0" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=8.0.0" } }, - "node_modules/bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "node_modules/autocannon": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/autocannon/-/autocannon-7.15.0.tgz", + "integrity": "sha512-NaP2rQyA+tcubOJMFv2+oeW9jv2pq/t+LM6BL3cfJic0HEfscEcnWgAyU5YovE/oTHUzAgTliGdLPR+RQAWUbg==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "chalk": "^4.1.0", + "char-spinner": "^1.0.1", + "cli-table3": "^0.6.0", + "color-support": "^1.1.1", + "cross-argv": "^2.0.0", + "form-data": "^4.0.0", + "has-async-hooks": "^1.0.0", + "hdr-histogram-js": "^3.0.0", + "hdr-histogram-percentiles-obj": "^3.0.0", + "http-parser-js": "^0.5.2", + "hyperid": "^3.0.0", + "lodash.chunk": "^4.2.0", + "lodash.clonedeep": "^4.5.0", + "lodash.flatten": "^4.4.0", + "manage-path": "^2.0.0", + "on-net-listen": "^1.1.1", + "pretty-bytes": "^5.4.1", + "progress": "^2.0.3", + "reinterval": "^1.1.0", + "retimer": "^3.0.0", + "semver": "^7.3.2", + "subarg": "^1.0.0", + "timestring": "^6.0.0" }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "bin": { + "autocannon": "autocannon.js" } }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" }, "engines": { - "node": ">= 0.8" + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "dev": true, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.1.2", - "cli-boxes": "^3.0.0", - "string-width": "^5.0.1", - "type-fest": "^2.5.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 14.15.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" } }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "dependencies": { + "object.assign": "^4.1.0" } }, - "node_modules/boxen/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==", + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/boxen/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen/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==", + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/boxen/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==", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "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==", + "node_modules/baileys": { + "version": "6.7.23", + "resolved": "https://registry.npmjs.org/baileys/-/baileys-6.7.23.tgz", + "integrity": "sha512-y/qGIdZyIdVBD76uHX9YnYcqEywJSmPgIYG40YcLBoo1uuoQS+MU2IpfP6ZFodv5girKbhvp4+ID9J59QtQN3g==", + "hasInstallScript": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "@cacheable/node-cache": "^1.4.0", + "@hapi/boom": "^9.1.3", + "async-mutex": "^0.5.0", + "axios": "^1.6.0", + "libsignal": "git+https://github.com/whiskeysockets/libsignal-node.git", + "music-metadata": "^11.7.0", + "pino": "^9.6", + "protobufjs": "^7.2.4", + "ws": "^8.13.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "node": ">=20.0.0" }, - "bin": { - "browserslist": "cli.js" + "peerDependencies": { + "audio-decode": "^2.1.3", + "jimp": "^1.6.0", + "link-preview-js": "^3.0.0", + "sharp": "*" }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "peerDependenciesMeta": { + "audio-decode": { + "optional": true }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "jimp": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "link-preview-js": { + "optional": true } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" } }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "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/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } } }, - "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-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "license": "MIT" - }, - "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==", - "license": "MIT" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", + "node_modules/bare-fs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", + "license": "Apache-2.0", "dependencies": { - "run-applescript": "^7.0.0" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { - "node": ">=18" + "bare": ">=1.16.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" + "peerDependencies": { + "bare-buffer": "*" }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, - "node_modules/bytestreamjs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", - "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6.0.0" - } + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "license": "Apache-2.0" }, - "node_modules/c12": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", - "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", - "license": "MIT", - "optional": true, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "license": "Apache-2.0", "dependencies": { - "chokidar": "^5.0.0", - "confbox": "^0.2.4", - "defu": "^6.1.6", - "dotenv": "^17.3.1", - "exsolve": "^1.0.8", - "giget": "^3.2.0", - "jiti": "^2.6.1", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "perfect-debounce": "^2.1.0", - "pkg-types": "^2.3.0", - "rc9": "^3.0.1" + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" }, "peerDependencies": { - "magicast": "*" + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-stream/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" }, "peerDependenciesMeta": { - "magicast": { + "react-native-b4a": { "optional": true } } }, - "node_modules/c12/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "optional": true, + "node_modules/bare-url": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.6.tgz", + "integrity": "sha512-iQxPClE07hETVpbRoX7JXX3v/ZQViCxe/SYCxylRLzdEx1xJAufPptfiOqR8tqiCtmbtMDANKWszzjLu1PMAZQ==", + "license": "Apache-2.0", "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "bare-path": "^3.0.0" } }, - "node_modules/c12/node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "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/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/c12/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "license": "MIT", - "optional": true, + "node_modules/baseline-browser-mapping": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", + "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", + "license": "Apache-2.0", "bin": { - "jiti": "lib/jiti-cli.mjs" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/c12/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "license": "MIT", - "optional": true, "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "node": ">=10.0.0" } }, - "node_modules/cac": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", - "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, "engines": { - "node": ">=20.19.0" + "node": ">= 18" } }, - "node_modules/cacheable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.3.tgz", - "integrity": "sha512-iffYMX4zxKp54evOH27fm92hs+DeC1DhXmNVN8Tr94M/iZIV42dqTHSR2Ik4TOSPyOAwKr7Yu3rN9ALoLkbWyQ==", + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cacheable/memory": "^2.0.8", - "@cacheable/utils": "^2.4.0", - "hookified": "^1.15.0", - "keyv": "^5.6.0", - "qified": "^0.6.0" + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" } }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "node_modules/better-sqlite3-session-store": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/better-sqlite3-session-store/-/better-sqlite3-session-store-0.1.0.tgz", + "integrity": "sha512-O4EO5jOGTEa/c1DbZpP3C7VTDLSWe5lrOu1S/j86ipdGZxrSb8bSUVuRgWCgl/SCgEGmyeEqvlMY9HtyOSMOWA==", + "license": "GPL-3.0-only", + "dependencies": { + "date-fns": "2.16.1" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" + "node": "*" } }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, + "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", - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, "engines": { - "node": ">=14.16" + "node": "*" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacheable-request/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, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "file-uri-to-path": "1.0.0" } }, - "node_modules/cacheable-request/node_modules/mimic-response": { + "node_modules/birpc": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "dev": true, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 6" + } + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "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==", + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "ms": "2.0.0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "devOptional": true, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "node_modules/bonjour-service": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.3.tgz", + "integrity": "sha512-2Kd5UYlFUVgAKMTyuBLl6w49wqfOnbxHqmuH0oCl/n7TfAikR0zoowNOP5BU4dfXmm+Vr9JyEN370auSMx+CNg==", "dev": true, "license": "MIT", "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dev": true, "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, + "node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "license": "MIT", "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001787", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", - "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", + "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==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "funding": [ { "type": "opencollective", @@ -16266,786 +13417,896 @@ }, { "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "url": "https://tidelift.com/funding/github/npm/browserslist" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0" + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "license": "MIT", "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/char-spinner": { + "node_modules/buffer-equal-constant-time": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/char-spinner/-/char-spinner-1.0.1.tgz", - "integrity": "sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==", - "dev": true, - "license": "ISC" + "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/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" } }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">= 0.8" } }, - "node_modules/character-reference-invalid": { + "node_modules/bytestreamjs": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", + "optional": true, "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "node_modules/c12/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" + "optional": true, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://dotenvx.com" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/c12/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, + "optional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "optional": true, "engines": { - "node": ">= 8.10.0" + "node": ">= 20.19.0" }, "funding": { + "type": "individual", "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", "license": "MIT", "engines": { - "node": ">=6.0" + "node": ">=20.19.0" } }, - "node_modules/chromium-bidi": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", - "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", - "license": "Apache-2.0", + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "license": "MIT", "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" + "node": ">=14.16" } }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "license": "MIT", "dependencies": { - "source-map": "~0.6.0" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">= 10.0" + "node": ">=14.16" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/cacheable-request/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", - "engines": { - "node": ">=6" + "dependencies": { + "json-buffer": "3.0.1" } }, - "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==", + "node_modules/cacheable-request/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "string-width": "^4.2.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" }, "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "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==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/clone-regexp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", - "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "is-regexp": "^3.0.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/clone-regexp/node_modules/is-regexp": { + "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" } }, - "node_modules/codec-parser": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/codec-parser/-/codec-parser-2.5.0.tgz", - "integrity": "sha512-Ru9t80fV8B0ZiixQl8xhMTLru+dzuis/KQld32/x5T/+3LwZb0/YvQdSKytX9JqCnRdiupvAvyYJINKrXieziQ==", - "license": "LGPL-3.0-or-later", - "optional": true, - "peer": true - }, - "node_modules/codemirror-theme-vars": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/codemirror-theme-vars/-/codemirror-theme-vars-0.1.2.tgz", - "integrity": "sha512-WTau8X2q58b0SOAY9DO+iQVw8JKVEgyQIqArp2D732tcc+pobbMta3bnVMdQdmgwuvNrOFFr6HoxPRoQOgooFA==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "license": "MIT", + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "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==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" + "license": "MIT", + "engines": { + "node": ">=10" } }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "node_modules/char-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/char-spinner/-/char-spinner-1.0.1.tgz", + "integrity": "sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==", "dev": true, - "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" + "license": "ISC" }, - "node_modules/combine-promises": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", - "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", - "dev": true, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", - "engines": { - "node": ">=10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "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==", + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "dev": true, "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, "engines": { - "node": ">= 6" + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, - "license": "ISC" + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "license": "MIT", + "node_modules/cheerio/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "license": "MIT", "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } ], "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "engines": { + "node": ">=8" } }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", "license": "MIT" }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "dev": true, "license": "MIT", "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" + "source-map": "~0.6.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" + "node": ">= 10.0" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, + "node_modules/clean-css/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": ">=0.10.0" } }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=6" } }, - "node_modules/connect/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "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==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/connect/node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "string-width": "^4.2.3" }, "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/connect/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/cli-progress/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/cli-progress/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": { - "ee-first": "1.1.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/connect/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, "engines": { - "node": ">= 0.6" + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/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": "^14.18.0 || >=16.10.0" + "node": ">=8" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", "dependencies": { - "safe-buffer": "5.2.1" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=20" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/cliui/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": ">= 0.6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/cliui/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/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/cliui/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": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-text-to-clipboard": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", - "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", - "dev": true, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "dev": true, + "node_modules/cliui/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": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 14.15.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" }, - "peerDependencies": { - "webpack": "^5.1.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/copy-webpack-plugin/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", + "node_modules/clone-deep/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "is-buffer": "^1.1.5" }, "engines": { - "node": ">=10.13.0" + "node": ">=0.10.0" } }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", "license": "MIT", "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "is-regexp": "^3.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, + "node_modules/clone-regexp/node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", "license": "MIT", "engines": { "node": ">=12" @@ -17054,1845 +14315,1885 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/core-js": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", - "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true, - "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/codemirror-theme-vars": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/codemirror-theme-vars/-/codemirror-theme-vars-0.1.2.tgz", + "integrity": "sha512-WTau8X2q58b0SOAY9DO+iQVw8JKVEgyQIqArp2D732tcc+pobbMta3bnVMdQdmgwuvNrOFFr6HoxPRoQOgooFA==", "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "dev": true, "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "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/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "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": { - "object-assign": "^4", - "vary": "^1" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "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/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" } }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "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/combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", + "dev": true, "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" + "engines": { + "node": ">=10" } }, - "node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "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", - "optional": true, - "peer": true, "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=14" - }, + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/cross-argv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cross-argv/-/cross-argv-2.0.0.tgz", - "integrity": "sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==", + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true, - "license": "MIT" - }, - "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": ">= 6" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.6" } }, - "node_modules/css-blank-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">= 0.8.0" } }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" + "ms": "2.0.0" } }, - "node_modules/css-declaration-sorter": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", - "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" + "node": ">= 0.6" } }, - "node_modules/css-has-pseudo": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", - "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" ], - "license": "MIT-0", + "license": "MIT", "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, "engines": { - "node": ">=18" + "node": ">=12" }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" } }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, "engines": { - "node": ">=4" + "node": ">= 0.10.0" } }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true, "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": ">=0.8" } }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", - "dev": true, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "lightningcss": { - "optional": true - } + "ms": "2.0.0" } }, - "node_modules/css-prefers-color-scheme": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "safe-buffer": "5.2.1" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": ">= 0.6" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": ">= 0.6" } }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cssdb": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.8.0.tgz", - "integrity": "sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ], - "license": "MIT-0" + "license": "MIT" }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "node_modules/copy-text-to-clipboard": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", + "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", + "dev": true, "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "dev": true, "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/cssnano" + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "postcss": "^8.4.31" + "webpack": "^5.1.0" } }, - "node_modules/cssnano-preset-advanced": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "node_modules/copy-webpack-plugin/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": "MIT", + "license": "ISC", "dependencies": { - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.0", - "cssnano-preset-default": "^6.1.2", - "postcss-discard-unused": "^6.0.5", - "postcss-merge-idents": "^6.0.3", - "postcss-reduce-idents": "^6.0.3", - "postcss-zindex": "^6.0.2" + "is-glob": "^4.0.3" }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=10.13.0" } }, - "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependencies": { - "postcss": "^8.4.31" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "node_modules/copy-webpack-plugin/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": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">= 4" } }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" + "node": ">=12" }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "dev": true, - "license": "CC0-1.0" - }, - "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==", - "license": "MIT" - }, - "node_modules/curve25519-js": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", - "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==", - "license": "MIT" - }, - "node_modules/cytoscape": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", - "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">=0.10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, "license": "MIT", "dependencies": { - "cose-base": "^1.0.0" + "browserslist": "^4.28.1" }, - "peerDependencies": { - "cytoscape": "^3.2.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "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/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { - "cose-base": "^2.2.0" + "object-assign": "^4", + "vary": "^1" }, - "peerDependencies": { - "cytoscape": "^3.2.0" + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", "license": "MIT", "dependencies": { - "layout-base": "^2.0.0" + "layout-base": "^1.0.0" } }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", + "node_modules/cross-argv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cross-argv/-/cross-argv-2.0.0.tgz", + "integrity": "sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==", + "dev": true, + "license": "MIT" + }, + "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": { - "internmap": "1 - 2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" + "node": ">= 8" } }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" + "node_modules/crossws": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.10.tgz", + "integrity": "sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA==", + "license": "MIT", + "peerDependencies": { + "srvx": ">=0.11.5" }, - "engines": { - "node": ">=12" + "peerDependenciesMeta": { + "srvx": { + "optional": true + } } }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-path": "1 - 3" + "type-fest": "^1.0.1" }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "d3-array": "^3.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, + "license": "MIT", "dependencies": { - "delaunator": "5" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "node_modules/css-declaration-sorter": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", + "dev": true, "license": "ISC", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" } }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", + "node_modules/css-has-pseudo": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, "engines": { - "node": ">= 10" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/d3-dsv/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", "dependencies": { - "d3-dsv": "1 - 3" + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=12" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" + "node_modules/cssdb": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.9.0.tgz", + "integrity": "sha512-J8jOU/hLjaXcO1LldOLraJSQpfLXRKof0I7mtbRyOy2AAXgqst0x9rlgi2qXeD6d0ou3ZLqcPAMqYVbpCbrxEw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-color": "1 - 3" + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, "engines": { - "node": ">=12" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, "engines": { - "node": ">=12" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } + "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==", + "license": "MIT" }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" + "node_modules/curve25519-js": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz", + "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==", + "license": "MIT" }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "license": "MIT", + "engines": { + "node": ">=0.10" } }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "cose-base": "^1.0.0" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "cytoscape": "^3.2.0" } }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" + "cose-base": "^2.2.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" + "peerDependencies": { + "cytoscape": "^3.2.0" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" + "layout-base": "^2.0.0" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "license": "ISC", "dependencies": { - "d3-array": "2 - 3" + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", "dependencies": { - "d3-time": "1 - 3" + "internmap": "1 - 2" }, "engines": { "node": ">=12" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", "engines": { "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" } }, - "node_modules/d3-zoom": { + "node_modules/d3-brush": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" + "d3-selection": "3", + "d3-transition": "3" }, "engines": { "node": ">=12" } }, - "node_modules/dagre-d3-es": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", - "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", - "license": "MIT", + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", "engines": { - "node": ">= 14" + "node": ">=12" } }, - "node_modules/date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", - "license": "MIT", - "engines": { - "node": ">=0.11" + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "engines": { + "node": ">=12" } }, - "node_modules/dayjs": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", - "license": "MIT" - }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", "dependencies": { - "ms": "2.0.0" + "delaunator": "5" + }, + "engines": { + "node": ">=12" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", "dependencies": { - "character-entities": "^2.0.0" + "d3-dispatch": "1 - 3", + "d3-selection": "3" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=12" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", "dependencies": { - "mimic-response": "^3.1.0" + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" }, - "engines": { - "node": ">=10" + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">=12" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 10" } }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "license": "MIT", - "engines": { - "node": ">=18" + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" } }, - "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==", - "dev": true, - "license": "MIT", + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=12" } }, - "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==", - "dev": true, - "license": "MIT", + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "d3-array": "2.5.0 - 3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "license": "MIT" + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" + "d3-color": "1 - 3" }, "engines": { - "node": ">= 14" + "node": ">=12" } }, - "node_modules/delaunator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", - "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" + "engines": { + "node": ">=12" } }, - "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", + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=12" } }, - "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", + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "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==", - "dev": true, - "license": "MIT" + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/detect-port": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", - "dev": true, - "license": "MIT", + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" + "d3-path": "^3.1.0" }, "engines": { - "node": ">= 4.0.0" + "node": ">=12" } }, - "node_modules/detect-port/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "d3-array": "2 - 3" }, "engines": { - "node": ">=6.0" + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=12" } }, - "node_modules/detect-port/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==", - "dev": true, - "license": "MIT" + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/devframe": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/devframe/-/devframe-0.5.4.tgz", - "integrity": "sha512-dbHU/LuptR1aMXcizjHUeY3gu7qVaRQoFYqbbyyGuO6Y+avFA4uQtwinHtG1qa7lRel/7b8JrBDm0nbnxU3vqg==", - "license": "MIT", + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", "dependencies": { - "@valibot/to-json-schema": "^1.7.0", - "birpc": "^4.0.0", - "cac": "^7.0.0", - "h3": "2.0.1-rc.22", - "mrmime": "^2.0.1", - "nostics": "^0.2.0", - "pathe": "^2.0.3", - "valibot": "^1.4.1", - "ws": "^8.21.0" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.0.0" + "engines": { + "node": ">=12" }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.132.0.tgz", - "integrity": "sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=12" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.132.0.tgz", - "integrity": "sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg==", - "cpu": [ - "arm64" - ], + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.132.0.tgz", - "integrity": "sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ==", - "cpu": [ - "arm64" - ], + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 14" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.132.0.tgz", - "integrity": "sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg==", - "cpu": [ - "x64" - ], + "node_modules/date-fns": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", + "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.132.0.tgz", - "integrity": "sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw==", - "cpu": [ - "x64" - ], + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.132.0.tgz", - "integrity": "sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w==", - "cpu": [ - "arm" - ], + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.10.0" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.132.0.tgz", - "integrity": "sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ==", - "cpu": [ - "arm" - ], + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.132.0.tgz", - "integrity": "sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw==", - "cpu": [ - "arm64" - ], + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "mimic-response": "^3.1.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.132.0.tgz", - "integrity": "sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw==", - "cpu": [ - "arm64" - ], + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=4.0.0" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.132.0.tgz", - "integrity": "sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA==", - "cpu": [ - "ppc64" - ], + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.10.0" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.132.0.tgz", - "integrity": "sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA==", - "cpu": [ - "riscv64" - ], + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.132.0.tgz", - "integrity": "sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg==", - "cpu": [ - "riscv64" - ], + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.132.0.tgz", - "integrity": "sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ==", - "cpu": [ - "s390x" - ], + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=10" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.132.0.tgz", - "integrity": "sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg==", - "cpu": [ - "x64" - ], + "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==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.132.0.tgz", - "integrity": "sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ==", - "cpu": [ - "x64" - ], + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=8" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.132.0.tgz", - "integrity": "sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ==", - "cpu": [ - "arm64" - ], + "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==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.132.0.tgz", - "integrity": "sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw==", - "cpu": [ - "wasm32" - ], + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 14" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.132.0.tgz", - "integrity": "sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw==", - "cpu": [ - "arm64" - ], + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "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", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.4.0" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.132.0.tgz", - "integrity": "sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA==", - "cpu": [ - "ia32" - ], + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.8" } }, - "node_modules/devframe/node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.132.0.tgz", - "integrity": "sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ==", - "cpu": [ - "x64" - ], + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6" } }, - "node_modules/devframe/node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/devframe/node_modules/nostics": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nostics/-/nostics-0.2.0.tgz", - "integrity": "sha512-/WQpI46UMbqvy1okYb+V+9wW3J8/m6GJ33wm691n/tyi6YtJiZ6ssJjENAU7y4evfYrrgYN9HllKDzPvffil1w==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.21", - "oxc-parser": "^0.132.0", - "unplugin": "^3.0.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/devframe/node_modules/oxc-parser": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.132.0.tgz", - "integrity": "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-2.1.0.tgz", + "integrity": "sha512-epZuWb/6Q62L+nDHJc/hQAqf8pylsqgk3BpZXVBx1CDnr3nkrVNn73Uu1rXcFzkNcc+hkP3whuOg7JZYaQB65Q==", + "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.132.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "address": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/Boshen" + "bin": { + "detect": "dist/commonjs/bin/detect-port.js", + "detect-port": "dist/commonjs/bin/detect-port.js" }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.132.0", - "@oxc-parser/binding-android-arm64": "0.132.0", - "@oxc-parser/binding-darwin-arm64": "0.132.0", - "@oxc-parser/binding-darwin-x64": "0.132.0", - "@oxc-parser/binding-freebsd-x64": "0.132.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", - "@oxc-parser/binding-linux-arm64-musl": "0.132.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", - "@oxc-parser/binding-linux-x64-gnu": "0.132.0", - "@oxc-parser/binding-linux-x64-musl": "0.132.0", - "@oxc-parser/binding-openharmony-arm64": "0.132.0", - "@oxc-parser/binding-wasm32-wasi": "0.132.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", - "@oxc-parser/binding-win32-x64-msvc": "0.132.0" - } - }, - "node_modules/devframe/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 16.0.0" } }, - "node_modules/devframe/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "node_modules/devframe": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/devframe/-/devframe-0.7.9.tgz", + "integrity": "sha512-ZWSHN5uk6KwCsrCXhu373kdBnk5kxkgxmGpD8tvv5DablgUOfWz+JrP4ufufTrns6PtQFP1Nh5S7sffWoLLp8w==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" + "@valibot/to-json-schema": "^1.7.1", + "birpc": "^4.0.0", + "crossws": "^0.4.10", + "destr": "^2.0.5", + "h3": "2.0.1-rc.22", + "mrmime": "^2.0.1", + "nostics": "^1.2.0", + "pathe": "^2.0.3", + "ufo": "^1.6.4", + "valibot": "^1.4.2" }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "cac": "^7.0.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + }, + "cac": { + "optional": true + } } }, "node_modules/devlop": { @@ -18909,11 +16210,10 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1566079", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", - "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", - "license": "BSD-3-Clause", - "peer": true + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "license": "BSD-3-Clause" }, "node_modules/dezalgo": { "version": "1.0.4", @@ -18955,33 +16255,33 @@ } }, "node_modules/discord-api-types": { - "version": "0.38.43", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.43.tgz", - "integrity": "sha512-sSoBf/nK6m7BGtw65mi+QBuvEWaHE8MMziFLqWL+gT6ME/BLg34dRSVKS3Husx40uU06bvxUc3/X+D9Y6/zAbw==", + "version": "0.38.50", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.50.tgz", + "integrity": "sha512-J2n/bpIETX3DQ6AJ7/0xbsTLmYiJQtO/LKcXKC1YDbB56OUwtDbdXOFE8Q4g8jVGHBR2VAy1+D4ngaIgkMNV9w==", "license": "MIT", "workspaces": [ "scripts/actions/documentation" ] }, "node_modules/discord.js": { - "version": "14.25.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", - "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", + "version": "14.27.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.27.0.tgz", + "integrity": "sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A==", "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.13.0", + "@discordjs/builders": "^1.14.1", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", - "@discordjs/rest": "^2.6.0", + "@discordjs/rest": "^2.6.2", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", - "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.38.33", + "@sapphire/snowflake": "3.5.5", + "discord-api-types": "^0.38.49", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", - "magic-bytes.js": "^1.10.0", + "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", - "undici": "6.21.3" + "undici": "^6.27.0" }, "engines": { "node": ">=18" @@ -19066,9 +16366,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -19092,7 +16392,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "devOptional": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -19173,7 +16472,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -19192,15 +16490,15 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.334", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", - "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", + "version": "1.5.395", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.395.tgz", + "integrity": "sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==", "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "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/emojilib": { @@ -19240,29 +16538,17 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, "node_modules/end-of-stream": { @@ -19296,84 +16582,36 @@ } }, "node_modules/engine.io-client": { - "version": "6.6.5", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz", - "integrity": "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==", + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.20.1", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io-client/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/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 - } + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" } }, - "node_modules/engine.io/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/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } }, "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -19391,22 +16629,10 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "devOptional": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -19456,15 +16682,15 @@ } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "license": "MIT" }, "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==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -19489,9 +16715,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", - "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", + "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", "license": "MIT", "workspaces": [ "docs", @@ -19635,6 +16861,16 @@ "source-map": "~0.6.1" } }, + "node_modules/escodegen/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==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -19764,16 +17000,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/estree-util-to-js/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/estree-util-value-to-estree": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", @@ -19902,9 +17128,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -19933,18 +17159,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -20038,10 +17252,58 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", "license": "MIT" }, "node_modules/extend": { @@ -20082,29 +17344,21 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "pump": "^3.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/extract-zip/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/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -20154,9 +17408,9 @@ "license": "Unlicense" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "funding": [ { "type": "github", @@ -20214,6 +17468,23 @@ "pend": "~1.2.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==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/feed": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", @@ -20281,9 +17552,9 @@ } }, "node_modules/file-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -20376,23 +17647,68 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~2.0.0", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.4.1", + "on-finished": "~2.3.0", "parseurl": "~1.3.3", - "statuses": "~2.0.2", + "statuses": "~1.5.0", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -20427,6 +17743,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fitty": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/fitty/-/fitty-2.4.2.tgz", + "integrity": "sha512-GNhWgImK4+wEkgEZjBkQMyu5NLSmmryg/CaRP7zYby+TWzCrUou6BHL+iqbjKzJRXMyzuJkH+LBB1+lh4oO77g==", + "license": "MIT" + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -20497,6 +17819,34 @@ "node": ">=0.10.0" } }, + "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/foreground-child/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/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -20519,18 +17869,6 @@ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", "license": "MIT" }, - "node_modules/form-data/node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -20637,9 +17975,10 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -20647,7 +17986,7 @@ "universalify": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14" } }, "node_modules/fs-monkey": { @@ -20698,9 +18037,9 @@ } }, "node_modules/fuse.js": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.4.2.tgz", - "integrity": "sha512-LVbzjD4WA6UP5B1UnP8wuaXJiLnqMdM/E4fiJXTJ5haJ5b/MBNsK29h2fm6swEoQaVQjvYFWKLE2RanyZIoRVQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.5.0.tgz", + "integrity": "sha512-sQtrEfA+ez/3G0cCZecF70oqpCRttCexYUG4mUrtWL49ULUzUyxokt5kyqwtKzj1270RaKih+hcP3qLcumccow==", "license": "Apache-2.0", "engines": { "node": ">=10" @@ -20717,9 +18056,9 @@ "license": "BSD-3-Clause" }, "node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", + "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -20730,13 +18069,35 @@ "node": ">=18" } }, + "node_modules/gaxios/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/gaxios/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": ">= 12" + } + }, + "node_modules/gaxios/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/gaxios/node_modules/node-fetch": { @@ -20798,33 +18159,6 @@ "node": ">=10.3.0" } }, - "node_modules/geoip-lite/node_modules/ip-address": { - "version": "5.9.4", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz", - "integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==", - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "lodash": "^4.17.15", - "sprintf-js": "1.1.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/geoip-lite/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -20897,15 +18231,12 @@ } }, "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -20925,29 +18256,6 @@ "node": ">= 14" } }, - "node_modules/get-uri/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/get-uri/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/giget": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.0.tgz", @@ -21042,15 +18350,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -21098,10 +18397,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby/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/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -21138,14 +18447,15 @@ } }, "node_modules/googleapis-common": { - "version": "8.0.2-rc.0", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.2-rc.0.tgz", - "integrity": "sha512-JTcxRvmFa9Ec1uyfMEimEMeeKq1sHNZX3vn2qmoUMtnvixXXvcqTcbDZvEZXkEWpGlPlOf4joyep6/qs0BrLyg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.2.tgz", + "integrity": "sha512-5MXeQzIZaqCH7B+HJWqhQm946VARpZep6acbWSr/fcgF2cQANq7allgX+i/G0EqF0WyUxB277gtWMzRYHMl9tg==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", - "gaxios": "^7.0.0-rc.4", - "google-auth-library": "^10.0.0-rc.1", + "gaxios": "7.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", "qs": "^6.7.0", "url-template": "^2.0.8" }, @@ -21153,6 +18463,148 @@ "node": ">=18.0.0" } }, + "node_modules/googleapis-common/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/googleapis-common/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/googleapis-common/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/googleapis-common/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/googleapis-common/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/googleapis-common/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/googleapis-common/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/googleapis-common/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/googleapis-common/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/googleapis-common/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/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -21214,19 +18666,6 @@ "node": ">= 14.17" } }, - "node_modules/got/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -21258,9 +18697,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -21270,21 +18709,19 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/gray-matter/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "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": ">=0.10.0" + "node": ">=18" } }, - "node_modules/gray-matter/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -21407,21 +18844,21 @@ } }, "node_modules/hashery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.0.tgz", - "integrity": "sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", "license": "MIT", "dependencies": { - "hookified": "^1.14.0" + "hookified": "^1.15.0" }, "engines": { "node": ">=20" } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -21655,12 +19092,15 @@ } }, "node_modules/helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.3.0.tgz", + "integrity": "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==", "license": "MIT", "engines": { "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/EvanHahn" } }, "node_modules/hey-listen": { @@ -21695,9 +19135,9 @@ } }, "node_modules/hono": { - "version": "4.12.26", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz", - "integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==", + "version": "4.12.31", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.31.tgz", + "integrity": "sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -21807,9 +19247,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.6", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", - "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", + "integrity": "sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==", "dev": true, "license": "MIT", "dependencies": { @@ -21846,48 +19286,130 @@ "dev": true, "license": "MIT", "engines": { - "node": ">= 12" + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz", + "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "domutils": "^4.0.2", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" } }, - "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", "license": "MIT", "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" }, "engines": { - "node": ">=12" + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "node_modules/htmlparser2/node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", { "type": "github", "url": "https://github.com/sponsors/fb55" } ], - "license": "MIT", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "license": "BSD-2-Clause", "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/http-cache-semantics": { @@ -21959,33 +19481,19 @@ "node": ">= 14" } }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/http-proxy-agent/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", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/http-proxy-agent/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/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -22041,41 +19549,18 @@ "license": "ISC" }, "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==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/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": ">= 6" } }, - "node_modules/https-proxy-agent/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/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -22116,24 +19601,13 @@ "uuid-parse": "^1.1.0" } }, - "node_modules/hyperid/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -22152,9 +19626,9 @@ } }, "node_modules/idb-keyval": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", - "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", + "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==", "license": "Apache-2.0" }, "node_modules/ieee754": { @@ -22178,11 +19652,11 @@ "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, + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "license": "MIT", + "optional": true, "engines": { "node": ">= 4" } @@ -22210,7 +19684,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "devOptional": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -22223,6 +19696,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/import-lazy": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", @@ -22291,10 +19773,13 @@ "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/inline-style-parser": { "version": "0.2.7", @@ -22382,7 +19867,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "devOptional": true, "license": "MIT" }, "node_modules/is-binary-path": { @@ -22417,13 +19901,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -22554,22 +20038,45 @@ } }, "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", + "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", "license": "MIT", "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally/node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-installed-globally/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/is-ip": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", @@ -22587,9 +20094,9 @@ } }, "node_modules/is-network-error": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", - "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "dev": true, "license": "MIT", "engines": { @@ -22632,13 +20139,15 @@ } }, "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-obj": { @@ -22750,6 +20259,21 @@ "node": ">=0.10.0" } }, + "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/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -22768,6 +20292,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -22811,9 +20348,9 @@ } }, "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -22825,9 +20362,9 @@ } }, "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.4.tgz", + "integrity": "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -22840,9 +20377,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "funding": [ { "type": "github", @@ -22861,12 +20398,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -22926,9 +20457,9 @@ } }, "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -23010,17 +20541,23 @@ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, "node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { "node": ">=0.10.0" } }, + "node_modules/kiwi-schema": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/kiwi-schema/-/kiwi-schema-0.5.0.tgz", + "integrity": "sha512-X+FpfU0yTEtc6aTHS7VwbOpvQwRt70+pXXWRI5fd6CvWhe7pSVC854TVo4Zo0x5/wwcWj+/9KUlXpdcP0dY9AA==", + "license": "MIT", + "bin": { + "kiwic": "cli.js" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -23063,14 +20600,14 @@ } }, "node_modules/launch-editor": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", - "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" + "shell-quote": "^1.8.4" } }, "node_modules/layout-base": { @@ -23107,6 +20644,15 @@ "node": ">=6" } }, + "node_modules/libsignal": { + "version": "6.0.0", + "resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#bcea72df9ec34d9d9140ab30619cf479c7c144c7", + "license": "GPL-3.0", + "dependencies": { + "curve25519-js": "^0.0.4", + "protobufjs": "^7.5.5" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -23117,9 +20663,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -23132,23 +20678,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -23166,9 +20712,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -23186,9 +20732,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -23206,9 +20752,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -23226,9 +20772,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -23246,9 +20792,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -23266,9 +20812,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -23286,9 +20832,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -23306,9 +20852,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -23326,9 +20872,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -23346,9 +20892,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -23382,13 +20928,12 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "devOptional": true, "license": "MIT" }, "node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "funding": [ { "type": "github", @@ -23405,9 +20950,9 @@ } }, "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "license": "MIT", "engines": { "node": ">=6.11.5" @@ -23563,7 +21108,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "devOptional": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -23583,12 +21127,12 @@ } }, "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "license": "ISC", - "engines": { - "node": ">=12" + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/lz-string": { @@ -23607,18 +21151,15 @@ "license": "MIT" }, "node_modules/magic-regexp": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.10.0.tgz", - "integrity": "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/magic-regexp/-/magic-regexp-0.11.0.tgz", + "integrity": "sha512-LG77Z/gVnwz7oaDpD4heX6ryl+lcr4l1B2gnP4MMvt2pGhGC1Dfj7dl1pXpP4ih+VQFLuAadeKVa+lARAzfW+Q==", "license": "MIT", "dependencies": { - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12", - "mlly": "^1.7.2", + "magic-string": "^0.30.21", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", - "ufo": "^1.5.4", - "unplugin": "^2.0.0" + "unplugin": "^3.0.0" } }, "node_modules/magic-string": { @@ -23632,739 +21173,317 @@ }, "node_modules/magic-string-ast": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", - "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.19" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/magic-string-stack": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/magic-string-stack/-/magic-string-stack-1.1.0.tgz", - "integrity": "sha512-eAjQQ16Woyi71/6gQoLvn9Mte0JDoS5zUV/BMk0Pzs8Fou+nEuo5T0UbLWBhm3mXiK2YnFz2lFpEEVcLcohhVw==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/manage-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/manage-path/-/manage-path-2.0.0.tgz", - "integrity": "sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/markdown-exit": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/markdown-exit/-/markdown-exit-1.0.0-beta.9.tgz", - "integrity": "sha512-5tzrMKMF367amyBly131vm6eGuWRL2DjBqWaFmPzPbLyuxP0XOmyyyroOAIXuBAMF/3kZbbfqOxvW/SotqKqbQ==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5.0.0", - "@types/mdurl": "^2.0.0", - "entities": "^7.0.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - } - }, - "node_modules/markdown-exit/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-it": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", - "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.1", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it-footnote": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-4.0.0.tgz", - "integrity": "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ==", - "license": "MIT" - }, - "node_modules/markdown-it-github-alerts": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/markdown-it-github-alerts/-/markdown-it-github-alerts-1.0.1.tgz", - "integrity": "sha512-NNATF4QdoGI07hyCitoB2YqJ1YcNVCKT89ut2VtfFY9rkeFCXe/V2lOonKQLpJiq5DjiZZepf97BJx5xOjFIAw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "markdown-it": ">= 13.0.0" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "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/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, "engines": { - "node": ">=12" + "node": ">=20.19.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "node_modules/magic-string-stack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/magic-string-stack/-/magic-string-stack-1.1.0.tgz", + "integrity": "sha512-eAjQQ16Woyi71/6gQoLvn9Mte0JDoS5zUV/BMk0Pzs8Fou+nEuo5T0UbLWBhm3mXiK2YnFz2lFpEEVcLcohhVw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" + "@jridgewell/remapping": "^2.3.5", + "magic-string": "^0.30.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/manage-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/manage-path/-/manage-path-2.0.0.tgz", + "integrity": "sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==", + "dev": true, "license": "MIT" }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "dev": true, + "node_modules/markdown-exit": { + "version": "1.1.0-beta.2", + "resolved": "https://registry.npmjs.org/markdown-exit/-/markdown-exit-1.1.0-beta.2.tgz", + "integrity": "sha512-8CzMGVlFZ4DEfnc8KU+4ycUW2SIOuiXqCHD7z51ecVEi/weyc0f2ylQbCm4KoKuVlTZSuMUMnWT0hTyquZ7anQ==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/linkify-it": "^5.0.0", + "@types/mdurl": "^2.0.0", + "entities": "^7.0.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" } }, - "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", + "node_modules/markdown-exit/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "node": ">=0.12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" + "engines": { + "node": ">=16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "github", + "url": "https://github.com/sponsors/puzrin" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/markdown-it" } ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/markdown-it-footnote": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-4.0.0.tgz", + "integrity": "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ==", "license": "MIT" }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "node_modules/markdown-it-github-alerts": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-github-alerts/-/markdown-it-github-alerts-1.0.1.tgz", + "integrity": "sha512-NNATF4QdoGI07hyCitoB2YqJ1YcNVCKT89ut2VtfFY9rkeFCXe/V2lOonKQLpJiq5DjiZZepf97BJx5xOjFIAw==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "url": "https://github.com/sponsors/antfu" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "peerDependencies": { + "markdown-it": ">= 13.0.0" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 20" } }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "dev": true, + "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", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 0.4" } }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", - "devlop": "^1.1.0", + "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT" - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/mediabunny": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.47.0.tgz", - "integrity": "sha512-XQMZAcaKPkJ7hQ/Q2fvBdl3ZazQl2WVxDysUbJWh4PuAnLoerdsQBdPTDWdUdK6hh26LQ8Ue94MLLnmpWvlUYg==", - "license": "MPL-2.0", - "workspaces": [ - ".", - "packages/*" + "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dependencies": { - "@types/dom-mediacapture-transform": "^0.1.11", - "@types/dom-webcodecs": "0.1.13" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Vanilagy" - } - }, - "node_modules/memfs": { - "version": "4.57.2", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", - "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/fs-core": "4.57.2", - "@jsonjoy.com/fs-fsa": "4.57.2", - "@jsonjoy.com/fs-node": "4.57.2", - "@jsonjoy.com/fs-node-builtins": "4.57.2", - "@jsonjoy.com/fs-node-to-fsa": "4.57.2", - "@jsonjoy.com/fs-node-utils": "4.57.2", - "@jsonjoy.com/fs-print": "4.57.2", - "@jsonjoy.com/fs-snapshot": "4.57.2", - "@jsonjoy.com/json-pack": "^1.11.0", - "@jsonjoy.com/util": "^1.9.0", - "glob-to-regex.js": "^1.0.1", - "thingies": "^2.5.0", - "tree-dump": "^1.0.3", - "tslib": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/merge-deep": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", - "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/mermaid": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", - "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.1.1", - "@types/d3": "^7.4.3", - "@upsetjs/venn.js": "^2.0.0", - "cytoscape": "^3.33.1", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.14", - "dayjs": "^1.11.19", - "dompurify": "^3.3.1", - "es-toolkit": "^1.45.1", - "katex": "^0.16.25", - "khroma": "^2.1.0", - "marked": "^16.3.0", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mermaid/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mermaid/node_modules/katex": { - "version": "0.16.47", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", - "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { - "commander": "^8.3.0" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "bin": { - "katex": "cli.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -24377,29 +21496,14 @@ ], "license": "MIT", "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -24410,360 +21514,396 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", "dependencies": { - "decode-named-character-reference": "^1.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "dev": true, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", "license": "MIT" }, - "node_modules/micromark-extension-frontmatter": { + "node_modules/media-typer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "dev": true, + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-2.0.0.tgz", + "integrity": "sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==", "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">=18" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/express" } }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/mediabunny": { + "version": "1.50.8", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.50.8.tgz", + "integrity": "sha512-LgykLyQzhdpo0V2yw3UXmOpj+b4JAGdpHBwsPE6kjSt8Za0d1VllD+FV7EGHBcdV4+oHUAo+yrqbVAWxNSDCPQ==", + "license": "MPL-2.0", + "workspaces": [ + ".", + "packages/*" ], - "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/dom-mediacapture-transform": "^0.1.11", + "@types/dom-webcodecs": "0.1.13" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" } }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" + "node_modules/memfs": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", + "license": "Unlicense", + "dependencies": { + "fs-monkey": "1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "dev": true, + "node_modules/merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", "license": "MIT", "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "dev": true, + "node_modules/merge-deep/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "is-buffer": "^1.1.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "license": "MIT" }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "dev": true, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 8" } }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mermaid": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.0.tgz", + "integrity": "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==", "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.20", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "katex": "^0.16.45", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, + "node_modules/mermaid/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/mermaid/node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" } }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "funding": [ { "type": "GitHub Sponsors", @@ -24774,32 +21914,31 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "dev": true, "license": "MIT", "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "funding": [ { "type": "GitHub Sponsors", @@ -24810,31 +21949,30 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "dev": true, "license": "MIT", "dependencies": { + "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { + "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -24851,11 +21989,10 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -24872,11 +22009,10 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -24888,40 +22024,28 @@ } ], "license": "MIT" - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", @@ -24942,7 +22066,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -24963,7 +22087,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -24980,10 +22104,27 @@ ], "license": "MIT" }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "dev": true, "funding": [ { @@ -24997,20 +22138,14 @@ ], "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "dev": true, "funding": [ { @@ -25022,13 +22157,47 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -25049,7 +22218,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -25066,30 +22235,28 @@ ], "license": "MIT" }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", + "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", @@ -25110,7 +22277,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -25131,7 +22298,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -25148,67 +22315,29 @@ ], "license": "MIT" }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "dev": true, "funding": [ { @@ -25220,16 +22349,30 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, "license": "MIT", "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "dev": true, "funding": [ { @@ -25241,33 +22384,17 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25284,10 +22411,11 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25300,10 +22428,43 @@ ], "license": "MIT" }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25316,16 +22477,15 @@ ], "license": "MIT", "dependencies": { - "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25342,10 +22502,11 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25358,10 +22519,10 @@ ], "license": "MIT" }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", "dev": true, "funding": [ { @@ -25377,16 +22538,15 @@ "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", @@ -25407,7 +22567,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -25428,7 +22588,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -25445,31 +22605,33 @@ ], "license": "MIT" }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], "license": "MIT", "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-space/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "dev": true, "funding": [ { @@ -25481,12 +22643,17 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25499,16 +22666,15 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25519,16 +22685,70 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-title/node_modules/micromark-util-character": { + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25545,10 +22765,11 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25561,10 +22782,10 @@ ], "license": "MIT" }, - "node_modules/micromark-factory-whitespace": { + "node_modules/micromark-factory-destination": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -25577,33 +22798,12 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -25623,7 +22823,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { + "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -25639,11 +22839,10 @@ ], "license": "MIT" }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "dev": true, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -25656,15 +22855,16 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-character/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "dev": true, + "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -25675,12 +22875,16 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-util-chunked": { + "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -25691,15 +22895,13 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25710,12 +22912,24 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } }, - "node_modules/micromark-util-classify-character": { + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25729,14 +22943,14 @@ "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25753,10 +22967,11 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25769,10 +22984,11 @@ ], "license": "MIT" }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25785,14 +23001,15 @@ ], "license": "MIT", "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "node_modules/micromark-factory-space/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25803,15 +23020,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { + "node_modules/micromark-factory-title": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -25822,12 +23036,18 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-util-decode-string": { + "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -25840,13 +23060,11 @@ ], "license": "MIT", "dependencies": { - "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { + "node_modules/micromark-factory-title/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -25866,7 +23084,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { + "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -25882,10 +23100,10 @@ ], "license": "MIT" }, - "node_modules/micromark-util-encode": { + "node_modules/micromark-factory-whitespace": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -25896,13 +23114,18 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "dev": true, + "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -25915,20 +23138,14 @@ ], "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -25939,12 +23156,16 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-util-html-tag-name": { + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -25957,10 +23178,11 @@ ], "license": "MIT" }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25973,13 +23195,15 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "node_modules/micromark-util-character/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -25992,10 +23216,10 @@ ], "license": "MIT" }, - "node_modules/micromark-util-resolve-all": { + "node_modules/micromark-util-chunked": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -26008,13 +23232,29 @@ ], "license": "MIT", "dependencies": { - "micromark-util-types": "^2.0.0" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-sanitize-uri": { + "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -26028,11 +23268,11 @@ "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { + "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -26052,7 +23292,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { + "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -26068,10 +23308,10 @@ ], "license": "MIT" }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -26084,13 +23324,30 @@ ], "license": "MIT", "dependencies": { - "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -26106,11 +23363,10 @@ ], "license": "MIT" }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "dev": true, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -26121,12 +23377,18 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -26137,29 +23399,32 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/micromark/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 + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } - } + ], + "license": "MIT" }, - "node_modules/micromark/node_modules/micromark-factory-space": { + "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -26170,16 +23435,13 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } + "license": "MIT" }, - "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -26192,14 +23454,20 @@ ], "license": "MIT", "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" } }, - "node_modules/micromark/node_modules/micromark-util-symbol": { + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -26212,1014 +23480,915 @@ ], "license": "MIT" }, - "node_modules/micromark/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/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "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/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", - "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mixin-object": { + "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", - "license": "MIT", - "dependencies": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-object/node_modules/for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "license": "MIT", - "dependencies": { - "acorn": "^8.16.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.3" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/monaco-editor": { - "version": "0.55.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", - "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "dompurify": "3.2.7", - "marked": "14.0.0" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/monaco-editor/node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } + "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/monaco-editor/node_modules/marked": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", - "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "node_modules/mpg123-decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mpg123-decoder/-/mpg123-decoder-1.0.3.tgz", - "integrity": "sha512-+fjxnWigodWJm3+4pndi+KUg9TBojgn31DPk85zEsim7C6s0X5Ztc/hQYdytXkwuGXH+aB0/aEkG40Emukv6oQ==", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@wasm-audio-decoders/common": "9.0.7" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/eshaz" + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/mrmime": { + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, + "node_modules/micromark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/music-metadata": { - "version": "11.12.3", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.12.3.tgz", - "integrity": "sha512-n6hSTZkuD59qWgHh6IP5dtDlDZQXoxk/bcA85Jywg8Z1iFrlNgl2+GTFgjZyn52W5UgQpV42V4XqrQZZAMbZTQ==", + "node_modules/micromark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "buymeacoffee", - "url": "https://buymeacoffee.com/borewit" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { - "@borewit/text-codec": "^0.2.2", - "@tokenizer/token": "^0.3.0", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "file-type": "^21.3.1", - "media-typer": "^1.1.0", - "strtok3": "^10.3.4", - "token-types": "^6.1.2", - "uint8array-extras": "^1.5.0", - "win-guid": "^0.2.1" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=18" + "node": ">=8.6" } }, - "node_modules/music-metadata/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=8.6" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/music-metadata/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/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "mime": "cli.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=4" } }, - "node_modules/nanotar": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/nanotar/-/nanotar-0.3.0.tgz", - "integrity": "sha512-Kv2JYYiCzt16Kt5QwAc9BFG89xfPNBx+oQL4GQXD9nLqPkZBiNaqaCWtwnbk/q7UVsTYevvM1b0UF8zmEI4pCg==", - "license": "MIT" - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "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/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "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.4.0" + "node": ">= 0.6" } }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "devOptional": true, + "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", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" + "engines": { + "node": ">=6" } }, - "node_modules/node-abi": { - "version": "3.87.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", - "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/node-addon-api": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", - "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "node_modules/mini-css-extract-plugin": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", + "dev": true, "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, "engines": { - "node": "^18 || ^20 || >= 21" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { - "uuid": "8.3.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6.0.0" + "node": "*" } }, - "node_modules/node-cron/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "license": "MIT" }, - "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" + "node_modules/minimizer-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true } - ], + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, "engines": { - "node": ">=10.5.0" + "node": ">= 10.13.0" } }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "dev": true, + "node_modules/minimizer-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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": ">=18" + "node": ">=16 || 14 >=14.17" } }, - "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==", + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, - "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==", + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/node-pty": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", - "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", - "hasInstallScript": true, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", "dependencies": { - "node-addon-api": "^7.1.0" + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/node-pty/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, - "node_modules/node-wav": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/node-wav/-/node-wav-0.0.2.tgz", - "integrity": "sha512-M6Rm/bbG6De/gKGxOpeOobx/dnGuP0dz40adqx38boqHhlWssBJZgLCPBNtb9NkrmnKYiV04xELq+R6PFOnoLA==", + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4.4.0" - } - }, - "node_modules/nodemailer": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.11.tgz", - "integrity": "sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" } }, - "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/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" }, - "node_modules/normalize-url": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", - "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", - "dev": true, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, - "node_modules/nostics": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/nostics/-/nostics-0.3.0.tgz", - "integrity": "sha512-tP0hvxK4n2hZO9kK5Az7dLXIFukANDpcdtMae3tvtyRQun48gZIBVyXCJc8zk+qsdOpY7ngashH0ivQGOGzmeg==", + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", "dependencies": { - "magic-string": "^0.30.21", - "oxc-parser": "^0.132.0", - "unplugin": "^3.0.0" + "dompurify": "3.2.7", + "marked": "14.0.0" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.132.0.tgz", - "integrity": "sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw==", - "cpu": [ - "arm" - ], + "node_modules/monaco-editor/node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "bin": { + "marked": "bin/marked.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 18" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.132.0.tgz", - "integrity": "sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg==", - "cpu": [ - "arm64" - ], + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=4" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.132.0.tgz", - "integrity": "sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ==", - "cpu": [ - "arm64" - ], + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=10" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.132.0.tgz", - "integrity": "sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg==", - "cpu": [ - "x64" - ], + "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/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 6.0.0" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.132.0.tgz", - "integrity": "sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw==", - "cpu": [ - "x64" - ], + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.132.0.tgz", - "integrity": "sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w==", - "cpu": [ - "arm" + "node_modules/music-metadata": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.14.0.tgz", + "integrity": "sha512-RyOSq98kuVfXB1emJ+NjBF0av8Ph3oBuqNy+Z5sFFfLhjYrkBQEB53V8u+U0RNTVwNo20WoPUwNkfKwZfrOqmQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@borewit/text-codec": "^0.2.2", + "@tokenizer/token": "^0.3.0", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "file-type": "^21.3.4", + "media-typer": "^2.0.0", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0", + "win-guid": "^0.2.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.132.0.tgz", - "integrity": "sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ==", - "cpu": [ - "arm" - ], + "node_modules/music-metadata/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.132.0.tgz", - "integrity": "sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw==", - "cpu": [ - "arm64" + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.132.0.tgz", - "integrity": "sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw==", - "cpu": [ - "arm64" - ], + "node_modules/nanotar": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/nanotar/-/nanotar-0.3.0.tgz", + "integrity": "sha512-Kv2JYYiCzt16Kt5QwAc9BFG89xfPNBx+oQL4GQXD9nLqPkZBiNaqaCWtwnbk/q7UVsTYevvM1b0UF8zmEI4pCg==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.6" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.132.0.tgz", - "integrity": "sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA==", - "cpu": [ - "ppc64" - ], + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.4.0" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.132.0.tgz", - "integrity": "sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA==", - "cpu": [ - "riscv64" - ], + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.132.0.tgz", - "integrity": "sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg==", - "cpu": [ - "riscv64" - ], + "node_modules/node-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "semver": "^7.3.5" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=10" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.132.0.tgz", - "integrity": "sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ==", - "cpu": [ - "s390x" - ], + "node_modules/node-addon-api": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz", + "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18 || ^20 || >= 21" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.132.0.tgz", - "integrity": "sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/node-cron": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.6.0.tgz", + "integrity": "sha512-Si/bzYiKRHOB8/a99T2+SDGN582ONDMSTlJr5oCkT6GtnqPjZ2s10eoQRYkW9ZHwjVxONL+W8Fb+qR0AHMQsdg==", + "license": "ISC", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.132.0.tgz", - "integrity": "sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ==", - "cpu": [ - "x64" + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=10.5.0" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.132.0.tgz", - "integrity": "sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ==", - "cpu": [ - "arm64" - ], + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.132.0.tgz", - "integrity": "sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw==", - "cpu": [ - "wasm32" - ], + "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", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "whatwg-url": "^5.0.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.132.0.tgz", - "integrity": "sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw==", - "cpu": [ - "arm64" - ], + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "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", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.132.0.tgz", - "integrity": "sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA==", - "cpu": [ - "ia32" - ], + "node_modules/node-pty": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", + "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", + "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "node-addon-api": "^7.1.0" } }, - "node_modules/nostics/node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.132.0.tgz", - "integrity": "sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ==", - "cpu": [ - "x64" - ], + "node_modules/node-pty/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/nostics/node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "node_modules/nodemailer": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.3.tgz", + "integrity": "sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" } }, - "node_modules/nostics/node_modules/oxc-parser": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.132.0.tgz", - "integrity": "sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg==", + "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", - "dependencies": { - "@oxc-project/types": "^0.132.0" - }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.132.0", - "@oxc-parser/binding-android-arm64": "0.132.0", - "@oxc-parser/binding-darwin-arm64": "0.132.0", - "@oxc-parser/binding-darwin-x64": "0.132.0", - "@oxc-parser/binding-freebsd-x64": "0.132.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.132.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.132.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.132.0", - "@oxc-parser/binding-linux-arm64-musl": "0.132.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.132.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.132.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.132.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.132.0", - "@oxc-parser/binding-linux-x64-gnu": "0.132.0", - "@oxc-parser/binding-linux-x64-musl": "0.132.0", - "@oxc-parser/binding-openharmony-arm64": "0.132.0", - "@oxc-parser/binding-wasm32-wasi": "0.132.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.132.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.132.0", - "@oxc-parser/binding-win32-x64-msvc": "0.132.0" - } - }, - "node_modules/nostics/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nostics/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" }, "node_modules/npm-run-path": { "version": "4.0.1", @@ -27274,9 +24443,9 @@ } }, "node_modules/null-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -27326,6 +24495,23 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/nypm": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.8.tgz", + "integrity": "sha512-Q9K4Diu6l5u6xJQogeFSs/zKtyMSgFKFtRQV+tHP4kL7KPm2grpBU0dFIwFaXwNxN0MtfKWc43VpCugAa+LPsw==", + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.2.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -27386,9 +24572,9 @@ "license": "MIT" }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" @@ -27409,24 +24595,6 @@ "ufo": "^1.6.1" } }, - "node_modules/ogg-opus-decoder": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/ogg-opus-decoder/-/ogg-opus-decoder-1.7.3.tgz", - "integrity": "sha512-w47tiZpkLgdkpa+34VzYD8mHUj8I9kfWVZa82mBbNwDvB1byfLXSSzW/HxA4fI3e9kVlICSpXGFwMLV1LPdjwg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@wasm-audio-decoders/common": "9.0.7", - "@wasm-audio-decoders/opus-ml": "0.0.2", - "codec-parser": "2.5.0", - "opus-decoder": "0.7.11" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/eshaz" - } - }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", @@ -27561,6 +24729,21 @@ } } }, + "node_modules/openai/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/openai/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/opencollective-postinstall": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", @@ -27580,42 +24763,27 @@ "opener": "bin/opener-bin.js" } }, - "node_modules/opus-decoder": { - "version": "0.7.11", - "resolved": "https://registry.npmjs.org/opus-decoder/-/opus-decoder-0.7.11.tgz", - "integrity": "sha512-+e+Jz3vGQLxRTBHs8YJQPRPc1Tr+/aC6coV/DlZylriA29BdHQAYXhvNRKtjftof17OFng0+P4wsFIqQu3a48A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@wasm-audio-decoders/common": "9.0.7" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/eshaz" - } - }, "node_modules/otplib": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.0.tgz", - "integrity": "sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.1.tgz", + "integrity": "sha512-o5CxfDw6bh7hoDv0NUUIcc0RqzJ9ipfUrzeKheKJ+vs4rXZnDlA9n4a/7R1cDjpmLjKLix4BgNVRmoDkm5rLSQ==", "license": "MIT", "dependencies": { - "@otplib/core": "13.4.0", - "@otplib/hotp": "13.4.0", - "@otplib/plugin-base32-scure": "13.4.0", - "@otplib/plugin-crypto-noble": "13.4.0", - "@otplib/totp": "13.4.0", - "@otplib/uri": "13.4.0" + "@otplib/core": "13.4.1", + "@otplib/hotp": "13.4.1", + "@otplib/plugin-base32-scure": "13.4.1", + "@otplib/plugin-crypto-noble": "13.4.1", + "@otplib/totp": "13.4.1", + "@otplib/uri": "13.4.1" } }, "node_modules/oxc-parser": { - "version": "0.131.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.131.0.tgz", - "integrity": "sha512-SJ3/7ZPbgie8dr5Z9BI/M51zZbpXba+hRSG0MDzVwMW5CRQg2fjYE0jHGlLX4eeiibGgC/mzoDFKSDHwVZEHRQ==", + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.140.0.tgz", + "integrity": "sha512-h6QFWd6lBMfjESqgQ27GjzrSDb0qbznp7VDQqp2zvgsrWut4vcchyMIzOVXvGQ2GMZgKw9RWrFNWv9WqGL0p7Q==", "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.131.0" + "@oxc-project/types": "^0.140.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -27624,38 +24792,47 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.131.0", - "@oxc-parser/binding-android-arm64": "0.131.0", - "@oxc-parser/binding-darwin-arm64": "0.131.0", - "@oxc-parser/binding-darwin-x64": "0.131.0", - "@oxc-parser/binding-freebsd-x64": "0.131.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.131.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.131.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.131.0", - "@oxc-parser/binding-linux-arm64-musl": "0.131.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.131.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.131.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.131.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.131.0", - "@oxc-parser/binding-linux-x64-gnu": "0.131.0", - "@oxc-parser/binding-linux-x64-musl": "0.131.0", - "@oxc-parser/binding-openharmony-arm64": "0.131.0", - "@oxc-parser/binding-wasm32-wasi": "0.131.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.131.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.131.0", - "@oxc-parser/binding-win32-x64-msvc": "0.131.0" + "@oxc-parser/binding-android-arm-eabi": "0.140.0", + "@oxc-parser/binding-android-arm64": "0.140.0", + "@oxc-parser/binding-darwin-arm64": "0.140.0", + "@oxc-parser/binding-darwin-x64": "0.140.0", + "@oxc-parser/binding-freebsd-x64": "0.140.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.140.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.140.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.140.0", + "@oxc-parser/binding-linux-arm64-musl": "0.140.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.140.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.140.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.140.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.140.0", + "@oxc-parser/binding-linux-x64-gnu": "0.140.0", + "@oxc-parser/binding-linux-x64-musl": "0.140.0", + "@oxc-parser/binding-openharmony-arm64": "0.140.0", + "@oxc-parser/binding-wasm32-wasi": "0.140.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.140.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.140.0", + "@oxc-parser/binding-win32-x64-msvc": "0.140.0" } }, "node_modules/oxc-walker": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/oxc-walker/-/oxc-walker-0.7.0.tgz", - "integrity": "sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oxc-walker/-/oxc-walker-1.0.0.tgz", + "integrity": "sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ==", "license": "MIT", "dependencies": { - "magic-regexp": "^0.10.0" + "magic-regexp": "^0.11.0" }, "peerDependencies": { - "oxc-parser": ">=0.98.0" + "oxc-parser": ">=0.98.0", + "rolldown": ">=1.0.0" + }, + "peerDependenciesMeta": { + "oxc-parser": { + "optional": true + }, + "rolldown": { + "optional": true + } } }, "node_modules/p-cancelable": { @@ -27743,19 +24920,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-queue/node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", @@ -27785,12 +24949,16 @@ } }, "node_modules/p-timeout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", - "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": ">=8" } }, "node_modules/p-try": { @@ -27821,29 +24989,28 @@ "node": ">= 14" } }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/pac-proxy-agent/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/pac-proxy-agent/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": { - "ms": "^2.1.3" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/pac-proxy-agent/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/pac-resolver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", @@ -27876,10 +25043,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", "license": "MIT" }, "node_modules/pako": { @@ -27903,7 +25076,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "devOptional": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -27943,7 +25115,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -27990,6 +25161,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -28076,6 +25259,28 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "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/path-scurry/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/path-to-regexp": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", @@ -28086,7 +25291,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -28135,12 +25339,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -28257,13 +25461,13 @@ "license": "MIT" }, "node_modules/playwright-chromium": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.59.1.tgz", - "integrity": "sha512-aTsPenkxsr9np4vIHuMEND6comqepVvzbL0MwkozFNliwGZjTqrBUQ7TF6Ay1ZIU/e7rcUpGsCTUG+nqwxG2Xw==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.61.1.tgz", + "integrity": "sha512-YK3K8JakUv5Hq5WjY6oU/XwuD/041s9XVtoTKTrZHYRY72DOAsni/iXye7iBS8lgYWUZENt/6tBnpmhaSg72ug==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.59.1" + "playwright-core": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -28273,9 +25477,9 @@ } }, "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -28328,9 +25532,9 @@ "license": "0BSD" }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", + "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==", "funding": [ { "type": "opencollective", @@ -28347,7 +25551,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -28382,9 +25586,9 @@ } }, "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -28637,9 +25841,9 @@ } }, "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -28677,9 +25881,9 @@ } }, "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -28813,9 +26017,9 @@ } }, "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -28853,9 +26057,9 @@ } }, "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -28979,33 +26183,6 @@ "webpack": "^5.0.0" } }, - "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/postcss-logical": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", @@ -29183,9 +26360,9 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -29211,9 +26388,9 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -29264,9 +26441,9 @@ } }, "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -29351,9 +26528,9 @@ } }, "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -29731,9 +26908,9 @@ } }, "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -29830,9 +27007,9 @@ } }, "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "dev": true, "license": "MIT", "dependencies": { @@ -29844,9 +27021,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -29950,9 +27127,9 @@ } }, "node_modules/pptxgenjs/node_modules/@types/node": { - "version": "22.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz", - "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -29973,12 +27150,6 @@ "node": ">=16.x" } }, - "node_modules/pptxgenjs/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -30156,9 +27327,9 @@ } }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -30173,9 +27344,9 @@ "license": "ISC" }, "node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -30227,35 +27398,52 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/proxy-agent/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/proxy-agent/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": { - "ms": "^2.1.3" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/proxy-agent/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/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/proxy-from-env": { + "node_modules/proxy-agent/node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/public-ip": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/public-ip/-/public-ip-8.0.0.tgz", @@ -30316,76 +27504,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/puppeteer": { - "version": "24.37.5", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.37.5.tgz", - "integrity": "sha512-3PAOIQLceyEmn1Fi76GkGO2EVxztv5OtdlB1m8hMUZL3f8KDHnlvXbvCXv+Ls7KzF1R0KdKBqLuT/Hhrok12hQ==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "2.13.0", - "chromium-bidi": "14.0.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1566079", - "puppeteer-core": "24.37.5", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/puppeteer-core": { - "version": "24.40.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.40.0.tgz", - "integrity": "sha512-MWL3XbUCfVgGR0gRsidzT6oKJT2QydPLhMITU6HoVWiiv4gkb6gJi3pcdAa8q4HwjBTbqISOWVP4aJiiyUJvag==", + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.13.0", + "@puppeteer/browsers": "2.13.2", "chromium-bidi": "14.0.0", "debug": "^4.4.3", - "devtools-protocol": "0.0.1581282", - "typed-query-selector": "^2.12.1", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.1", - "ws": "^8.19.0" + "ws": "^8.20.0" }, "engines": { "node": ">=18" } }, - "node_modules/puppeteer-core/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/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1581282", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz", - "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==", - "license": "BSD-3-Clause" - }, - "node_modules/puppeteer-core/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/puppeteer-extra": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz", @@ -30468,29 +27604,6 @@ } } }, - "node_modules/puppeteer-extra-plugin-stealth/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/puppeteer-extra-plugin-stealth/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/puppeteer-extra-plugin-user-data-dir": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", @@ -30518,170 +27631,61 @@ } } }, - "node_modules/puppeteer-extra-plugin-user-data-dir/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/puppeteer-extra-plugin-user-data-dir/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/puppeteer-extra-plugin-user-preferences": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", - "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.3", - "puppeteer-extra-plugin-user-data-dir": "^2.4.1" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "playwright-extra": "*", - "puppeteer-extra": "*" - }, - "peerDependenciesMeta": { - "playwright-extra": { - "optional": true - }, - "puppeteer-extra": { - "optional": true - } - } - }, - "node_modules/puppeteer-extra-plugin-user-preferences/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/puppeteer-extra-plugin-user-preferences/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/puppeteer-extra-plugin/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/puppeteer-extra-plugin/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/puppeteer-extra/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=12" } }, - "node_modules/puppeteer-extra/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/puppeteer/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "peer": true, + "node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "glob": "^7.1.3" }, - "engines": { - "node": ">=6.0" + "bin": { + "rimraf": "bin.js" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/puppeteer/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", - "optional": true, - "peer": true - }, - "node_modules/puppeteer/node_modules/puppeteer-core": { - "version": "24.37.5", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.5.tgz", - "integrity": "sha512-ybL7iE78YPN4T6J+sPLO7r0lSByp/0NN6PvfBEql219cOnttoTFzCWKiBOjstXSqi/OKpwae623DWAsL7cn2MQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/puppeteer-extra-plugin-user-preferences": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", + "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", + "license": "MIT", "dependencies": { - "@puppeteer/browsers": "2.13.0", - "chromium-bidi": "14.0.0", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1566079", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.4.1", - "ws": "^8.19.0" + "debug": "^4.1.1", + "deepmerge": "^4.2.2", + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-data-dir": "^2.4.1" }, "engines": { - "node": ">=18" + "node": ">=8" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } } }, "node_modules/pvtsutils": { @@ -30705,27 +27709,22 @@ } }, "node_modules/qified": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/qified/-/qified-0.6.0.tgz", - "integrity": "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", "license": "MIT", "dependencies": { - "hookified": "^1.14.0" + "hookified": "^2.1.1" }, "engines": { "node": ">=20" } }, - "node_modules/qoa-format": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/qoa-format/-/qoa-format-1.0.1.tgz", - "integrity": "sha512-dMB0Z6XQjdpz/Cw4Rf6RiBpQvUSPCfYlQMWvmuWlWkAT7nDQD29cVZ1SwDUB6DYJSitHENwbt90lqfI+7bvMcw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@thi.ng/bitstream": "^2.2.12" - } + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "license": "MIT" }, "node_modules/qrcode": { "version": "1.5.4", @@ -30764,6 +27763,12 @@ "wrap-ansi": "^6.2.0" } }, + "node_modules/qrcode/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/qrcode/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -30825,6 +27830,20 @@ "node": ">=8" } }, + "node_modules/qrcode/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/qrcode/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -30881,12 +27900,13 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -30993,9 +28013,9 @@ } }, "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -31023,6 +28043,12 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/rc9": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", @@ -31249,6 +28275,18 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -31286,6 +28324,15 @@ "node": ">=4" } }, + "node_modules/recast/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/recma-build-jsx": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", @@ -31484,9 +28531,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", - "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -31682,9 +28729,9 @@ } }, "node_modules/remotion": { - "version": "4.0.479", - "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.479.tgz", - "integrity": "sha512-ch+hXW5SsX6CRPIktL2NasOGExHnynnf0FVbq7Pi2UXzGspmbHnirmtCqHqdj+/3lNDYn7qzBriPzh8Z5kkDRA==", + "version": "4.0.496", + "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.496.tgz", + "integrity": "sha512-B84022w11rPFoFr/uMQ9GaJYlgwXtCMo19CYlD6Pc2CL8hqhSMGPmso6a04zu7HwVTKApj8ZQy5XeFBAnR2/Uw==", "license": "SEE LICENSE IN LICENSE.md", "peerDependencies": { "react": ">=16.8.0", @@ -31868,13 +28915,12 @@ "license": "MIT" }, "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "devOptional": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/resolve-global": { @@ -31966,9 +29012,9 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "license": "ISC", "dependencies": { @@ -31976,9 +29022,6 @@ }, "bin": { "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/robust-predicates": { @@ -31988,12 +29031,12 @@ "license": "Unlicense" }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -32003,27 +29046,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/rolldown/node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -32063,33 +29106,10 @@ "node": ">= 18" } }, - "node_modules/router/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/router/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/router/node_modules/path-to-regexp": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", - "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -32311,15 +29331,6 @@ "node": ">=4" } }, - "node_modules/section-matter/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -32342,9 +29353,9 @@ } }, "node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -32393,16 +29404,25 @@ "node": ">= 0.8.0" } }, - "node_modules/send/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==", + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/serialize-javascript": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", - "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -32508,6 +29528,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/serve-index/node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -32535,6 +29565,13 @@ "node": ">= 0.6" } }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -32640,47 +29677,52 @@ "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, + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", "license": "Apache-2.0", "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.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" + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shebang-command": { @@ -32705,9 +29747,9 @@ } }, "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==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, "license": "MIT", "engines": { @@ -32718,17 +29760,17 @@ } }, "node_modules/shiki": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz", - "integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz", + "integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==", "license": "MIT", "dependencies": { - "@shikijs/core": "4.2.0", - "@shikijs/engine-javascript": "4.2.0", - "@shikijs/engine-oniguruma": "4.2.0", - "@shikijs/langs": "4.2.0", - "@shikijs/themes": "4.2.0", - "@shikijs/types": "4.2.0", + "@shikijs/core": "4.3.1", + "@shikijs/engine-javascript": "4.3.1", + "@shikijs/engine-oniguruma": "4.3.1", + "@shikijs/langs": "4.3.1", + "@shikijs/themes": "4.3.1", + "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, @@ -32737,14 +29779,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -32756,13 +29798,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -32859,23 +29901,10 @@ "simple-concat": "^1.0.0" } }, - "node_modules/simple-yenc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/simple-yenc/-/simple-yenc-1.0.4.tgz", - "integrity": "sha512-5gvxpSd79e9a3V4QDYUqnqxeD4HGlhCakVpb6gMnDD7lexJggSBJRBO5h52y/iJrdXRilX9UCuDaIJhSWm5OWw==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/eshaz" - } - }, "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -32883,7 +29912,7 @@ "totalist": "^3.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" } }, "node_modules/sisteransi": { @@ -32956,7 +29985,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "devOptional": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -32991,129 +30019,35 @@ "ws": "~8.21.0" } }, - "node_modules/socket.io-adapter/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/socket.io-adapter/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/socket.io-client": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", - "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-client/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/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/socket.io/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/socket.io-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10.0.0" } }, - "node_modules/socket.io/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/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -33126,23 +30060,13 @@ "websocket-driver": "^0.7.4" } }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", "license": "MIT", "dependencies": { - "ip-address": "^10.0.1", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -33164,29 +30088,15 @@ "node": ">= 14" } }, - "node_modules/socks-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/socks-proxy-agent/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", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/socks-proxy-agent/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/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", @@ -33207,12 +30117,13 @@ } }, "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==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -33234,6 +30145,15 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-support/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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -33276,31 +30196,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/spdy-transport/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy-transport/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==", - "dev": true, - "license": "MIT" - }, "node_modules/spdy-transport/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -33316,31 +30211,6 @@ "node": ">= 6" } }, - "node_modules/spdy/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy/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==", - "dev": true, - "license": "MIT" - }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -33351,9 +30221,9 @@ } }, "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, "node_modules/srcset": { @@ -33370,9 +30240,9 @@ } }, "node_modules/srvx": { - "version": "0.11.16", - "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.16.tgz", - "integrity": "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw==", + "version": "0.11.22", + "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.22.tgz", + "integrity": "sha512-LqZxxBDMKuMAZzFzJnDCkFOrs9MZQZr0LvHiO/SuSZVdQaXD7xQ5UWTUxheJrQPve1qk9MG2B/yttUvJxw8egQ==", "license": "MIT", "bin": { "srvx": "bin/srvx.mjs" @@ -33422,9 +30292,9 @@ } }, "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==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -33448,6 +30318,24 @@ "license": "MIT" }, "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/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==", @@ -33461,6 +30349,39 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width/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/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -33502,6 +30423,19 @@ "node": ">=8" } }, + "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-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -33530,9 +30464,9 @@ } }, "node_modules/stripe": { - "version": "22.2.1", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-22.2.1.tgz", - "integrity": "sha512-ULAtq25USBEx3yeN5zimEkfYVFETVMP5om25Ryr8ol11P62imaCZLBIEW78T7zm7Wg3wnZi+80S72yf3LCpNhw==", + "version": "22.3.2", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-22.3.2.tgz", + "integrity": "sha512-O13QOvgEIQvDlTy6Ubb5kB980wpbhmoZNsgCXKILjCMZS67f+bW+6w99k3gnSi/N1lkryoj1WYdpGT5Wc5edjg==", "license": "MIT", "engines": { "node": ">=18" @@ -33685,24 +30619,6 @@ "node": ">=14.18.0" } }, - "node_modules/superagent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -33716,13 +30632,6 @@ "node": ">=4.0.0" } }, - "node_modules/superagent/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==", - "dev": true, - "license": "MIT" - }, "node_modules/supertest": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", @@ -33777,13 +30686,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "dev": true, "license": "MIT" }, "node_modules/svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.4.tgz", + "integrity": "sha512-GsNRis4e8jxn2Y9ENz/8lbJ93CstG8svtMnuRaHbiF2LTJ5tK0/q3t/URPq9Zc7zVWBJnNnJMIp6bevK7bSmNg==", "dev": true, "license": "MIT", "dependencies": { @@ -33817,9 +30725,9 @@ } }, "node_modules/tapable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -33830,9 +30738,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -33841,12 +30749,6 @@ "tar-stream": "^2.1.4" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -33908,29 +30810,15 @@ "node": "^12.20.0 || >=14.13.1" } }, - "node_modules/telegraf/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/telegraf/node_modules/p-timeout": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", + "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10" } }, - "node_modules/telegraf/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/telnyx": { "version": "5.51.0", "resolved": "https://registry.npmjs.org/telnyx/-/telnyx-5.51.0.tgz", @@ -33942,9 +30830,9 @@ } }, "node_modules/terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -33960,9 +30848,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -33981,12 +30869,39 @@ "webpack": "^5.1.0" }, "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, "@swc/core": { "optional": true }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, "esbuild": { "optional": true }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, "uglify-js": { "optional": true } @@ -34060,6 +30975,20 @@ "b4a": "^1.6.4" } }, + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/thingies": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", @@ -34073,1303 +31002,1708 @@ "type": "github", "url": "https://github.com/sponsors/streamich" }, - "peerDependencies": { - "tslib": "^2" + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/timestring": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/timestring/-/timestring-6.0.0.tgz", + "integrity": "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "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/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "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==", + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/twoslash": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/twoslash/-/twoslash-0.3.9.tgz", + "integrity": "sha512-rDclk+OtzuTX+tnea7DYLCkqGQ3eP0IyfD+kzUJ7t46X/NzlaxwrhecmEBNuSCuEn3V+n1PhcjUUQQ7gUJzX5Q==", "license": "MIT", "dependencies": { - "real-require": "^0.2.0" + "@typescript/vfs": "^1.6.4", + "twoslash-protocol": "0.3.9" + }, + "peerDependencies": { + "typescript": "^5.5.0 || ^6.0.0" } }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true, + "node_modules/twoslash-protocol": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/twoslash-protocol/-/twoslash-protocol-0.3.9.tgz", + "integrity": "sha512-9/iwp+CXOnjFMPQuPL5PkuRbZnDoNpBvtJCLs9t8kDYkL3YHujbvnHfZA1i5fApDftVEdBw+T/4F+dH5kIzpYQ==", "license": "MIT" }, - "node_modules/time-span": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", - "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "node_modules/twoslash-vue": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/twoslash-vue/-/twoslash-vue-0.3.9.tgz", + "integrity": "sha512-2zO1u4iPhZz9k7ysuDaJL1FUn4SHzm78ZF6/0Q4yFR2VP3NW0JwRpw/4cWqEM6ye3pDf8Zr9lVPBvsX4uK315A==", "license": "MIT", "dependencies": { - "convert-hrtime": "^5.0.0" + "@vue/language-core": "^3.2.6", + "twoslash": "0.3.9", + "twoslash-protocol": "0.3.9" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" }, + "peerDependencies": { + "typescript": "^5.5.0 || ^6.0.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/timestring": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/timestring/-/timestring-6.0.0.tgz", - "integrity": "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==", - "dev": true, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "node_modules/type-level-regexp": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz", + "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==", "license": "MIT" }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", "license": "MIT" }, - "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=18" + "node": ">=14.17" } }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "random-bytes": "~1.0.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">= 0.8" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "license": "MIT", "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "node": ">=18" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/unconfig": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.5.0.tgz", + "integrity": "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==", "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@quansync/fs": "^1.0.0", + "defu": "^6.1.4", + "jiti": "^2.6.1", + "quansync": "^1.0.0", + "unconfig-core": "7.5.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } + "node_modules/unconfig-core/node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "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==", + "node_modules/unconfig/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/unconfig/node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/unctx": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.5.0.tgz", + "integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==", "license": "MIT", - "engines": { - "node": ">=0.6" + "optional": true, + "dependencies": { + "acorn": "^8.15.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21", + "unplugin": "^2.3.11" } }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "node_modules/unctx/node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", "license": "MIT", + "optional": true, "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "node": ">=18.12.0" } }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18.17" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/tree-dump": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" + "node_modules/unhead": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-3.2.3.tgz", + "integrity": "sha512-BBkKvthUOufEJzG4C4u5NCdxnGMNaQdb//oPDI8lkv/6qj0faEqnPIvIPejt3FnSE501LX25Gbe2MVgKDuFnyQ==", + "license": "MIT", + "dependencies": { + "hookable": "^6.1.1", + "unplugin": "^3.3.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "url": "https://github.com/sponsors/harlan-zw" }, "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "vite": ">=6.4.2" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } } }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=4" } }, - "node_modules/ts-dedent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", - "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6.10" + "node": ">=4" } }, - "node_modules/ts-mixer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" - }, - "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/tsyringe": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", - "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^1.9.3" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=4" } }, - "node_modules/tsyringe/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, - "license": "0BSD" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" - }, - "node_modules/twoslash": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/twoslash/-/twoslash-0.3.8.tgz", - "integrity": "sha512-OeDz0kDl8sqPUN3nr7gqcvOs70f5lZsdhKYTX3/SgB9OvdadzzoYJI/4SBXhXV1HG8E9fLc+e17itoRYTxmoig==", + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@typescript/vfs": "^1.6.4", - "twoslash-protocol": "0.3.8" - }, - "peerDependencies": { - "typescript": "^5.5.0 || ^6.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/twoslash-protocol": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/twoslash-protocol/-/twoslash-protocol-0.3.8.tgz", - "integrity": "sha512-HmvAHoiEviK8LqvAQyc9/irkdvwTUiR1fHmNwH/0gq8EHxyBt4PWVPixjEXg6wJu1u6yBrILEWXGK9Kw58/8yQ==", - "license": "MIT" - }, - "node_modules/twoslash-vue": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/twoslash-vue/-/twoslash-vue-0.3.8.tgz", - "integrity": "sha512-5HZAnkQ1R6NXqW9mqsHx4aHVWPb5gb4gfEKiaNczi3q6U7vDZeVv9eONRuPs4qdCd5OJSAIpHeXxIDZmjr0Jww==", + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/language-core": "^3.2.6", - "twoslash": "0.3.8", - "twoslash-protocol": "0.3.8" + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "typescript": "^5.5.0 || ^6.0.0" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, "engines": { - "node": ">=12.20" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">= 0.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/type-is/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/type-level-regexp": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz", - "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==", - "license": "MIT" - }, - "node_modules/typed-query-selector": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz", - "integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==", - "license": "MIT" - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", "dev": true, "license": "MIT", "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">=14.17" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, - "node_modules/ufo": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", - "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", - "license": "MIT" - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { - "random-bytes": "~1.0.0" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">= 0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/unconfig": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.5.0.tgz", - "integrity": "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==", + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { - "@quansync/fs": "^1.0.0", - "defu": "^6.1.4", - "jiti": "^2.6.1", - "quansync": "^1.0.0", - "unconfig-core": "7.5.0" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/unconfig-core": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", - "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unocss": { + "version": "66.7.5", + "resolved": "https://registry.npmjs.org/unocss/-/unocss-66.7.5.tgz", + "integrity": "sha512-nAdmU8TwnQoiLnQjZ6Hm1GmHy9lTexKsAZNEJtZnxN9wyFRc1eLjQgmh9r7UEGxBQMQLD8OZOgmk5HpwObbh0Q==", "license": "MIT", "dependencies": { - "@quansync/fs": "^1.0.0", - "quansync": "^1.0.0" + "@unocss/cli": "66.7.5", + "@unocss/core": "66.7.5", + "@unocss/preset-attributify": "66.7.5", + "@unocss/preset-icons": "66.7.5", + "@unocss/preset-mini": "66.7.5", + "@unocss/preset-tagify": "66.7.5", + "@unocss/preset-typography": "66.7.5", + "@unocss/preset-uno": "66.7.5", + "@unocss/preset-web-fonts": "66.7.5", + "@unocss/preset-wind": "66.7.5", + "@unocss/preset-wind3": "66.7.5", + "@unocss/preset-wind4": "66.7.5", + "@unocss/transformer-attributify-jsx": "66.7.5", + "@unocss/transformer-compile-class": "66.7.5", + "@unocss/transformer-directives": "66.7.5", + "@unocss/transformer-variant-group": "66.7.5", + "@unocss/vite": "66.7.5" }, "funding": { "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unconfig-core/node_modules/quansync": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", - "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@unocss/astro": "66.7.5", + "@unocss/postcss": "66.7.5", + "@unocss/webpack": "66.7.5" + }, + "peerDependenciesMeta": { + "@unocss/astro": { + "optional": true }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" + "@unocss/postcss": { + "optional": true + }, + "@unocss/webpack": { + "optional": true } - ], - "license": "MIT" + } }, - "node_modules/unconfig/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "engines": { + "node": ">= 0.8" } }, - "node_modules/unconfig/node_modules/quansync": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", - "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true } - ], - "license": "MIT" + } }, - "node_modules/unctx": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.5.0.tgz", - "integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==", + "node_modules/unplugin-icons": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-23.0.1.tgz", + "integrity": "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==", "license": "MIT", - "optional": true, "dependencies": { - "acorn": "^8.15.0", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21", + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.1.0", + "local-pkg": "^1.1.2", + "obug": "^2.1.1", "unplugin": "^2.3.11" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + } } }, - "node_modules/undici": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", - "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "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/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, + "node_modules/unplugin-icons/node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, "engines": { - "node": ">=4" + "node": ">=18.12.0" } }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "dev": true, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, "engines": { - "node": ">=4" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, + "node_modules/unplugin-vue-components": { + "version": "32.1.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-32.1.0.tgz", + "integrity": "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==", "license": "MIT", "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "chokidar": "^5.0.0", + "local-pkg": "^1.2.0", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "tinyglobby": "^0.2.16", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1" }, "engines": { - "node": ">=4" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } } }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "dev": true, + "node_modules/unplugin-vue-components/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, "engines": { - "node": ">=4" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "dev": true, + "node_modules/unplugin-vue-components/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "dev": true, + "node_modules/unplugin-vue-markdown": { + "version": "32.0.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-markdown/-/unplugin-vue-markdown-32.0.0.tgz", + "integrity": "sha512-K9uiYJF9kvngrN/NRx8fVPZFXiqJR7tbnXQV4mVFxjcsKhuiL6+vVQ5woam59RqeCDprecBmbg+cCGADz0somA==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" + "@mdit-vue/plugin-component": "^3.0.2", + "@mdit-vue/plugin-frontmatter": "^3.0.2", + "@mdit-vue/types": "^3.0.2", + "markdown-exit": "^1.0.0-beta.9", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=22" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, + "node_modules/unplugin-vue-markdown/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "node_modules/unplugin-vue-markdown/node_modules/markdown-exit": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/markdown-exit/-/markdown-exit-1.0.0-beta.9.tgz", + "integrity": "sha512-5tzrMKMF367amyBly131vm6eGuWRL2DjBqWaFmPzPbLyuxP0XOmyyyroOAIXuBAMF/3kZbbfqOxvW/SotqKqbQ==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/linkify-it": "^5.0.0", + "@types/mdurl": "^2.0.0", + "entities": "^7.0.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" } }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "node_modules/untun": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/untun/-/untun-0.1.3.tgz", + "integrity": "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0" + "citty": "^0.1.5", + "consola": "^3.2.3", + "pathe": "^1.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "untun": "bin/untun.mjs" } }, - "node_modules/unist-util-position-from-estree": { + "node_modules/untun/node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/untun/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "license": "MIT" + }, + "node_modules/untyped": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz", + "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", "license": "MIT", + "optional": true, "dependencies": { - "@types/unist": "^3.0.0" + "citty": "^0.1.6", + "defu": "^6.1.4", + "jiti": "^2.4.2", + "knitwork": "^1.2.0", + "scule": "^1.3.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "untyped": "dist/cli.mjs" } }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "node_modules/untyped/node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", "license": "MIT", + "optional": true, "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "consola": "^3.2.3" } }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "node_modules/untyped/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "optional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/unist-util-visit-parents": { + "node_modules/update-notifier": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dev": true, "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/update-notifier/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" + "node": ">=14.16" }, - "engines": { - "node": ">=18.12.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unplugin-icons": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-23.0.1.tgz", - "integrity": "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==", + "node_modules/update-notifier/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==", + "dev": true, "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/utils": "^3.1.0", - "local-pkg": "^1.1.2", - "obug": "^2.1.1", - "unplugin": "^2.3.11" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@svgr/core": ">=7.0.0", - "@svgx/core": "^1.0.1", - "@vue/compiler-sfc": "^3.0.2", - "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@svgr/core": { - "optional": true - }, - "@svgx/core": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "svelte": { - "optional": true - } + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "node_modules/update-notifier/node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, "license": "MIT", "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { - "node": ">=20.19.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sxzz" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unplugin-utils/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/update-notifier/node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=8" } }, - "node_modules/unplugin-vue-components": { - "version": "32.1.0", - "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-32.1.0.tgz", - "integrity": "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==", + "node_modules/uqr": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.3.tgz", + "integrity": "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA==", + "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/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^5.0.0", - "local-pkg": "^1.2.0", - "magic-string": "^0.30.21", - "mlly": "^1.8.2", - "obug": "^2.1.1", - "picomatch": "^4.0.4", - "tinyglobby": "^0.2.16", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1" + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" }, "engines": { - "node": ">=20.19.0" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@nuxt/kit": "^3.2.2 || ^4.0.0", - "vue": "^3.0.0" + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { - "@nuxt/kit": { + "file-loader": { "optional": true } } }, - "node_modules/unplugin-vue-components/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "node_modules/url-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" + "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": { - "url": "https://paulmillr.com/funding/" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/unplugin-vue-components/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "ajv": "^6.9.1" } }, - "node_modules/unplugin-vue-components/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "node_modules/url-loader/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/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, "engines": { - "node": ">= 20.19.0" + "node": ">= 10.13.0" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/unplugin-vue-components/node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "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/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 4" } }, - "node_modules/unplugin/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 0.4.0" } }, - "node_modules/untun": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/untun/-/untun-0.1.3.tgz", - "integrity": "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==", + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", - "dependencies": { - "citty": "^0.1.5", - "consola": "^3.2.3", - "pathe": "^1.1.1" - }, "bin": { - "untun": "bin/untun.mjs" + "uuid": "dist/esm/bin/uuid" } }, - "node_modules/untun/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "node_modules/uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "dev": true, "license": "MIT" }, - "node_modules/untyped": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz", - "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", + "node_modules/valibot": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", + "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", "license": "MIT", - "optional": true, - "dependencies": { - "citty": "^0.1.6", - "defu": "^6.1.4", - "jiti": "^2.4.2", - "knitwork": "^1.2.0", - "scule": "^1.3.0" + "peerDependencies": { + "typescript": ">=5" }, - "bin": { - "untyped": "dist/cli.mjs" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/untyped/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", - "optional": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" + "engines": { + "node": ">= 0.8" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=14.16" + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" }, "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/update-notifier/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "dev": true, + "node_modules/vite-dev-rpc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", + "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", "license": "MIT", "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" + "birpc": "^4.0.0", + "vite-hot-client": "^2.2.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" + "url": "https://github.com/sponsors/antfu" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" } }, - "node_modules/update-notifier/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==", - "dev": true, + "node_modules/vite-hot-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", + "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" } }, - "node_modules/update-notifier/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-notifier/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, + "node_modules/vite-plugin-inspect": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", + "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "ansis": "^4.3.0", + "error-stack-parser-es": "^1.0.5", + "obug": "^2.1.1", + "ohash": "^2.0.11", + "open": "^11.0.0", + "perfect-debounce": "^2.1.0", + "sirv": "^3.0.2", + "unplugin-utils": "^0.3.1", + "vite-dev-rpc": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" + "url": "https://github.com/sponsors/antfu" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } } }, - "node_modules/update-notifier/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, + "node_modules/vite-plugin-inspect/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "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/uqr": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.3.tgz", - "integrity": "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA==", - "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" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "dev": true, + "node_modules/vite-plugin-inspect/node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "license": "MIT", "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/url-loader/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, + "node_modules/vite-plugin-remote-assets": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-remote-assets/-/vite-plugin-remote-assets-2.1.0.tgz", + "integrity": "sha512-8ajL5WG5BmYcC8zxeLOa3byCUG2AopKDAdNK7zStPHaRYYz1mxXBaeNFLu6vTEXj8UmXAsb5WlEmBBYwtlPEwA==", "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" + "debug": "^4.4.1", + "magic-string": "^0.30.17", + "node-fetch-native": "^1.6.7", + "ohash": "^2.0.11" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/url-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", + "url": "https://github.com/sponsors/antfu" + }, "peerDependencies": { - "ajv": "^6.9.1" + "vite": ">=5.0.0" } }, - "node_modules/url-loader/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/url-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, + "node_modules/vite-plugin-static-copy": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-4.1.1.tgz", + "integrity": "sha512-GrlA8YklrAfSyxJ4M3fdQLOo9oNkp56IM9FYgX/WtEgeIFkPwhu4wzpufBCIuNKCa6Fn77FkRdYxkHqV0FwjAw==", "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "chokidar": "^3.6.0", + "p-map": "^7.0.4", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.17" }, "engines": { - "node": ">= 10.13.0" + "node": "^22.0.0 || >=24.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", - "license": "BSD" - }, - "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/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" + "type": "github", + "url": "https://github.com/sponsors/sapphi-red" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/vite-plugin-static-copy/node_modules/p-map": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.6.tgz", + "integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==", "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/uuid": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/vite-plugin-vue-server-ref": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-server-ref/-/vite-plugin-vue-server-ref-1.0.0.tgz", + "integrity": "sha512-6d/JZVrnETM0xa0AVyEcI1bXFpEzQ1EPU5N/gDa7NtXo/7nfJWJhezcWq82Jih6Vf8xtGJjhi1w19AcXAtwmAg==", "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" + "dependencies": { + "debug": "^4.4.0", + "klona": "^2.0.6", + "mlly": "^1.7.4", + "ufo": "^1.5.4" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": ">=2.0.0", + "vue": "^3.0.0" } }, - "node_modules/uuid-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", - "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", - "dev": true, - "license": "MIT" + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } }, - "node_modules/valibot": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.1.tgz", - "integrity": "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g==", + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, "peerDependencies": { - "typescript": ">=5" + "typescript": "*" }, "peerDependenciesMeta": { "typescript": { @@ -35377,93 +32711,154 @@ } } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vue-resize": { + "version": "2.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", + "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "peerDependencies": { + "vue": "^3.0.0" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "node_modules/vue-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz", + "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" + "@babel/generator": "^8.0.0", + "@vue-macros/common": "^3.1.3", + "@vue/devtools-api": "^8.1.5", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.2.1", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "nostics": "^1.1.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.5", + "scule": "^1.3.0", + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2", + "yaml": "^2.9.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", + "pinia": "^3.0.4 || ^4.0.2", + "vite": "^7.3.0 || ^8.0.0", + "vue": "^3.5.34 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "dev": true, + "node_modules/vue-router/node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "node_modules/vue-router/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", "license": "MIT", "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" + "@babel/types": "^8.0.4" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/vue": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", - "integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", + "node_modules/vue-router/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.38", - "@vue/compiler-sfc": "3.5.38", - "@vue/runtime-dom": "3.5.38", - "@vue/server-renderer": "3.5.38", - "@vue/shared": "3.5.38" + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" }, - "peerDependencies": { - "typescript": "*" + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/vue-resize": { - "version": "2.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", - "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==", + "node_modules/vue-router/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", - "peerDependencies": { - "vue": "^3.0.0" + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/wasm-feature-detect": { @@ -35473,12 +32868,11 @@ "license": "Apache-2.0" }, "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" }, "engines": { @@ -35528,12 +32922,11 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.106.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.0.tgz", - "integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==", + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", @@ -35543,21 +32936,19 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", - "es-module-lexer": "^2.0.0", + "enhanced-resolve": "^5.22.2", + "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", + "loader-runner": "^4.3.2", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.0" }, "bin": { "webpack": "bin/webpack.js" @@ -35612,6 +33003,21 @@ "node": ">= 10" } }, + "node_modules/webpack-bundle-analyzer/node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/webpack-dev-middleware": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", @@ -35642,6 +33048,36 @@ } } }, + "node_modules/webpack-dev-middleware/node_modules/memfs": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.64.0.tgz", + "integrity": "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-fsa": "4.64.0", + "@jsonjoy.com/fs-node": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-to-fsa": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/fs-print": "4.64.0", + "@jsonjoy.com/fs-snapshot": "4.64.0", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/webpack-dev-middleware/node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -35670,9 +33106,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", - "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", + "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", "dev": true, "license": "MIT", "dependencies": { @@ -35694,7 +33130,7 @@ "graceful-fs": "^4.2.6", "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", + "launch-editor": "^2.14.1", "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", @@ -35741,13 +33177,29 @@ } }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", - "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/webpack-dev-server/node_modules/open": { @@ -35769,6 +33221,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webpack-merge": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", @@ -35799,16 +33267,6 @@ "node": ">=6" } }, - "node_modules/webpack-merge/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/webpack-merge/node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -35823,9 +33281,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -35837,6 +33295,15 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpackbar": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-7.0.0.tgz", @@ -35865,10 +33332,20 @@ } } }, + "node_modules/webpackbar/node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -35890,6 +33367,28 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -35937,88 +33436,111 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true, + "license": "MIT" + }, + "node_modules/win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", + "license": "MIT" + }, + "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/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/widest-line/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, + "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": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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/strip-ansi?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/win-guid": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", - "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", - "license": "MIT" + "node_modules/wrap-ansi/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/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==", + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrappy": { @@ -36041,9 +33563,9 @@ } }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -36062,16 +33584,16 @@ } }, "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", "license": "MIT", "dependencies": { - "is-wsl": "^3.1.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -36081,7 +33603,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -36168,30 +33689,79 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs/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/yargs/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/yargs/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/yargs/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/yauzl": { @@ -36217,6 +33787,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zigpty": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/zigpty/-/zigpty-0.2.1.tgz", + "integrity": "sha512-MR9JqJx2wf5f4wz8zpx050AlqrmWeIW+1h0SO5iEyhG3HFRjY5luC3szS2ux2EGuPjE5OU9ZAuiCBeMWBTrqZw==", + "workspaces": [ + "playground" + ] + }, "node_modules/zlibjs": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", @@ -36236,12 +33814,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } }, "node_modules/zwitch": { diff --git a/package.json b/package.json index 8d2e2f40..53038d0e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "AGPL-3.0-only", "main": "server/index.js", "engines": { - "node": ">=18.0.0" + "node": ">=20.19.0" }, "bin": { "neoagent": "bin/neoagent.js" @@ -68,9 +68,9 @@ "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@google/generative-ai": "^0.24.0", - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.29.0", "@remotion/cli": "^4.0.459", - "@slidev/cli": "^52.15.2", + "@slidev/cli": "^52.18.0", "@slidev/theme-default": "^0.25.0", "baileys": "^6.7.21", "bcrypt": "^6.0.0", @@ -78,7 +78,7 @@ "better-sqlite3-session-store": "^0.1.0", "cheerio": "^1.0.0-rc.12", "cors": "^2.8.5", - "discord.js": "^14.25.1", + "discord.js": "^14.27.0", "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", @@ -87,9 +87,9 @@ "googleapis": "^150.0.1", "helmet": "^8.0.0", "multer": "^1.4.5-lts.1", - "node-cron": "^3.0.3", + "node-cron": "^4.6.0", "node-pty": "^1.0.0", - "nodemailer": "^8.0.5", + "nodemailer": "^9.0.3", "openai": "^4.85.4", "otplib": "^13.4.0", "playwright-chromium": "^1.59.1", @@ -99,7 +99,7 @@ "puppeteer-extra-plugin-stealth": "^2.11.2", "qrcode": "^1.5.4", "remotion": "^4.0.459", - "sharp": "^0.34.5", + "sharp": "^0.35.3", "socket.io": "^4.8.1", "stripe": "^22.2.1", "telegraf": "^4.16.3", @@ -109,19 +109,26 @@ "ws": "^8.21.0" }, "overrides": { + "@hono/node-server": "^2.0.11", "axios": "^1.15.2", "basic-ftp": "^5.3.0", + "dompurify": "^3.4.12", + "fast-uri": "^3.1.4", "follow-redirects": "^1.16.0", - "hono": "^4.12.14", - "protobufjs": "^7.5.5", + "hono": "^4.12.31", + "ip-address": "^10.2.0", + "linkify-it": "^5.0.2", + "protobufjs": "^7.6.5", "serialize-javascript": "^7.0.5", - "undici": "^6.24.0", + "undici": "^6.27.0", + "uuid": "$uuid", "webpackbar": "^7.0.0", "ws": "^8.21.0" }, "devDependencies": { - "@docusaurus/core": "3.10.0", - "@docusaurus/preset-classic": "3.10.0", + "@docusaurus/core": "3.10.2", + "@docusaurus/preset-classic": "3.10.2", + "@types/node": "^20.19.0", "autocannon": "^7.15.0", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/runtime/paths.js b/runtime/paths.js index 72e4f0d9..391b2122 100644 --- a/runtime/paths.js +++ b/runtime/paths.js @@ -109,26 +109,69 @@ function readEnvFileRaw(envFile = ENV_FILE) { } } +function writeEnvFileAtomic(envFile, content) { + const resolved = path.resolve(envFile); + const directory = path.dirname(resolved); + const temporary = path.join( + directory, + `.${path.basename(resolved)}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`, + ); + fs.mkdirSync(directory, { recursive: true }); + let descriptor = null; + try { + descriptor = fs.openSync(temporary, 'wx', 0o600); + fs.writeFileSync(descriptor, content, 'utf8'); + fs.fsyncSync(descriptor); + fs.closeSync(descriptor); + descriptor = null; + fs.renameSync(temporary, resolved); + } finally { + if (descriptor !== null) { + try { fs.closeSync(descriptor); } catch {} + } + try { fs.rmSync(temporary, { force: true }); } catch {} + } +} + +function normalizeEnvKey(key) { + const normalized = String(key).replace(/[\r\n]/g, ''); + if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(normalized)) { + throw new Error(`Invalid environment variable name: ${normalized || '(empty)'}`); + } + return normalized; +} + function upsertEnvValue(envFile, key, value) { + const safeKey = normalizeEnvKey(key); + const safeValue = String(value).replace(/[\r\n]/g, ''); const raw = readEnvFileRaw(envFile); const lines = raw ? raw.split('\n') : []; let replaced = false; for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith(`${key}=`)) { - lines[i] = `${key}=${value}`; + if (lines[i].startsWith(`${safeKey}=`)) { + lines[i] = `${safeKey}=${safeValue}`; replaced = true; break; } } if (!replaced) { - lines.push(`${key}=${value}`); + lines.push(`${safeKey}=${safeValue}`); } const output = lines.filter((line, idx, arr) => idx !== arr.length - 1 || line !== '').join('\n') + '\n'; - fs.mkdirSync(path.dirname(envFile), { recursive: true }); - fs.writeFileSync(envFile, output, { mode: 0o600 }); + writeEnvFileAtomic(envFile, output); +} + +function removeEnvValue(envFile, key) { + const safeKey = normalizeEnvKey(key); + const raw = readEnvFileRaw(envFile); + if (!raw) return false; + const lines = raw.split('\n').filter((line) => !line.startsWith(`${safeKey}=`)); + const output = lines.filter((line, idx, arr) => idx !== arr.length - 1 || line !== '').join('\n') + '\n'; + writeEnvFileAtomic(envFile, output); + return true; } function generateSecret(bytes = 32) { @@ -247,6 +290,7 @@ module.exports = { ensureSecureRuntimeEnv, getDefaultVmBaseImageUrl, migrateLegacyRuntime, + removeEnvValue, upsertEnvValue, readEnvFileRaw, }; diff --git a/server/guest_agent.js b/server/guest_agent.js index c6dd6629..ade20e05 100644 --- a/server/guest_agent.js +++ b/server/guest_agent.js @@ -105,6 +105,26 @@ async function handle(res, work) { } } +async function handleRequest(req, res, work) { + const controller = new AbortController(); + const abort = () => { + if (!res.writableEnded) controller.abort('Guest runtime request disconnected.'); + }; + req.once('aborted', abort); + res.once('close', abort); + try { + const result = await work(controller.signal); + if (!res.headersSent && !res.writableEnded) res.json(result); + } catch (err) { + if (!res.headersSent && !res.writableEnded) { + res.status(controller.signal.aborted ? 499 : 500).json({ error: sanitizeError(err) }); + } + } finally { + req.removeListener('aborted', abort); + res.removeListener('close', abort); + } +} + app.use(requireToken); app.get('/health', (_req, res) => { @@ -118,7 +138,7 @@ app.get('/health', (_req, res) => { }); app.post('/exec', async (req, res) => { - await handle(res, async () => { + await handleRequest(req, res, async (signal) => { const command = String(req.body?.command || '').trim(); if (!command) { return { error: 'command is required' }; @@ -127,12 +147,14 @@ app.post('/exec', async (req, res) => { return cliExecutor.executeInteractive(command, req.body?.inputs || [], { cwd: req.body?.cwd, timeout: req.body?.timeout, + signal, }); } return cliExecutor.execute(command, { cwd: req.body?.cwd, timeout: req.body?.timeout, stdinInput: req.body?.stdin_input, + signal, }); }); }); @@ -192,43 +214,43 @@ function requireCapability(controller, name) { return controller; } -app.post('/browser/launch', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').launch(req.body || {}))); -app.post('/browser/navigate', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').navigate(req.body?.url, req.body || {}))); -app.post('/browser/screenshot', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').screenshot(req.body || {}))); -app.post('/browser/screenshot-jpeg', async (req, res) => handle(res, async () => { - const jpeg = await requireCapability(browserController, 'browser').screenshotJpeg(req.body?.quality, req.body || {}); +app.post('/browser/launch', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').launch({ ...(req.body || {}), signal }))); +app.post('/browser/navigate', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').navigate(req.body?.url, { ...(req.body || {}), signal }))); +app.post('/browser/screenshot', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').screenshot({ ...(req.body || {}), signal }))); +app.post('/browser/screenshot-jpeg', async (req, res) => handleRequest(req, res, async (signal) => { + const jpeg = await requireCapability(browserController, 'browser').screenshotJpeg(req.body?.quality, { ...(req.body || {}), signal }); return { contentType: 'image/jpeg', contentBase64: Buffer.from(jpeg).toString('base64'), }; })); -app.post('/browser/click', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false))); -app.post('/browser/click-point', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false))); -app.post('/browser/fill', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), req.body || {}))); -app.post('/browser/type-text', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').typeText(String(req.body?.text || ''), req.body || {}))); -app.post('/browser/press-key', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').pressKey(req.body?.key, req.body?.screenshot !== false))); -app.post('/browser/scroll', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').scroll(req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false))); -app.post('/browser/extract', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').extractContent(req.body || {}))); -app.post('/browser/execute', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').executeJS(req.body?.code))); +app.post('/browser/click', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false, { signal }))); +app.post('/browser/click-point', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false, { signal }))); +app.post('/browser/fill', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), { ...(req.body || {}), signal }))); +app.post('/browser/type-text', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').typeText(String(req.body?.text || ''), { ...(req.body || {}), signal }))); +app.post('/browser/press-key', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').pressKey(req.body?.key, req.body?.screenshot !== false, { signal }))); +app.post('/browser/scroll', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').scroll(req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false, { signal }))); +app.post('/browser/extract', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').extractContent({ ...(req.body || {}), signal }))); +app.post('/browser/execute', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').executeJS(req.body?.code, { signal }))); app.post('/browser/close', async (_req, res) => handle(res, () => requireCapability(browserController, 'browser').closeBrowser().then(() => ({ success: true })))); app.get('/android/status', async (_req, res) => handle(res, () => requireCapability(androidController, 'android').getStatus())); -app.post('/android/start', async (req, res) => handle(res, () => requireCapability(androidController, 'android').requestStartEmulator(req.body || {}))); -app.post('/android/stop', async (_req, res) => handle(res, () => requireCapability(androidController, 'android').stopEmulator())); -app.get('/android/devices', async (_req, res) => handle(res, async () => ({ devices: await requireCapability(androidController, 'android').listDevices() }))); -app.post('/android/screenshot', async (req, res) => handle(res, () => requireCapability(androidController, 'android').screenshot(req.body || {}))); -app.post('/android/observe', async (req, res) => handle(res, () => requireCapability(androidController, 'android').observe(req.body || {}))); -app.post('/android/ui-dump', async (req, res) => handle(res, () => requireCapability(androidController, 'android').dumpUi(req.body || {}))); -app.get('/android/apps', async (req, res) => handle(res, () => requireCapability(androidController, 'android').listApps({ includeSystem: req.query.includeSystem === 'true' }))); -app.post('/android/open-app', async (req, res) => handle(res, () => requireCapability(androidController, 'android').openApp(req.body || {}))); -app.post('/android/open-intent', async (req, res) => handle(res, () => requireCapability(androidController, 'android').openIntent(req.body || {}))); -app.post('/android/tap', async (req, res) => handle(res, () => requireCapability(androidController, 'android').tap(req.body || {}))); -app.post('/android/long-press', async (req, res) => handle(res, () => requireCapability(androidController, 'android').longPress(req.body || {}))); -app.post('/android/type', async (req, res) => handle(res, () => requireCapability(androidController, 'android').type(req.body || {}))); -app.post('/android/swipe', async (req, res) => handle(res, () => requireCapability(androidController, 'android').swipe(req.body || {}))); -app.post('/android/press-key', async (req, res) => handle(res, () => requireCapability(androidController, 'android').pressKey(req.body || {}))); -app.post('/android/wait-for', async (req, res) => handle(res, () => requireCapability(androidController, 'android').waitFor(req.body || {}))); -app.post('/android/shell', async (req, res) => handle(res, () => requireCapability(androidController, 'android').shell(req.body || {}))); +app.post('/android/start', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').requestStartEmulator({ ...(req.body || {}), signal }))); +app.post('/android/stop', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').stopEmulator({ signal }))); +app.get('/android/devices', async (req, res) => handleRequest(req, res, async (signal) => ({ devices: await requireCapability(androidController, 'android').listDevices({ signal }) }))); +app.post('/android/screenshot', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').screenshot({ ...(req.body || {}), signal }))); +app.post('/android/observe', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').observe({ ...(req.body || {}), signal }))); +app.post('/android/ui-dump', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').dumpUi({ ...(req.body || {}), signal }))); +app.get('/android/apps', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').listApps({ includeSystem: req.query.includeSystem === 'true', signal }))); +app.post('/android/open-app', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').openApp({ ...(req.body || {}), signal }))); +app.post('/android/open-intent', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').openIntent({ ...(req.body || {}), signal }))); +app.post('/android/tap', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').tap({ ...(req.body || {}), signal }))); +app.post('/android/long-press', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').longPress({ ...(req.body || {}), signal }))); +app.post('/android/type', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').type({ ...(req.body || {}), signal }))); +app.post('/android/swipe', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').swipe({ ...(req.body || {}), signal }))); +app.post('/android/press-key', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').pressKey({ ...(req.body || {}), signal }))); +app.post('/android/wait-for', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').waitFor({ ...(req.body || {}), signal }))); +app.post('/android/shell', async (req, res) => handleRequest(req, res, (signal) => requireCapability(androidController, 'android').shell({ ...(req.body || {}), signal }))); app.post('/android/install-apk', async (req, res) => { await handle(res, async () => { requireCapability(androidController, 'android'); diff --git a/server/http/middleware.js b/server/http/middleware.js index a7c016dc..4238c26f 100644 --- a/server/http/middleware.js +++ b/server/http/middleware.js @@ -145,6 +145,28 @@ function createSessionMiddleware({ secureCookies, trustProxy }) { }); } +function attachRequestSignal(req, res, next) { + const controller = new AbortController(); + const abort = () => { + if (!res.writableEnded && !controller.signal.aborted) { + const error = new Error('HTTP client disconnected.'); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + controller.abort(error); + } + }; + const cleanup = () => { + req.removeListener('aborted', abort); + res.removeListener('close', abort); + res.removeListener('finish', cleanup); + }; + req.signal = controller.signal; + req.once('aborted', abort); + res.once('close', abort); + res.once('finish', cleanup); + next(); +} + function applyHttpMiddleware(app, { secureCookies, trustProxy, sessionMiddleware, validateOrigin }) { const jsonBody = require('express').json({ limit: '10mb', @@ -179,6 +201,7 @@ function applyHttpMiddleware(app, { secureCookies, trustProxy, sessionMiddleware } app.use(helmet(buildHelmetOptions({ secureCookies }))); + app.use(attachRequestSignal); app.use( cors((req, callback) => { const requestPath = `${req.originalUrl || req.url || req.path || ''}`.split('?')[0]; @@ -255,6 +278,7 @@ function applyHttpMiddleware(app, { secureCookies, trustProxy, sessionMiddleware } module.exports = { + attachRequestSignal, applyHttpMiddleware, createSessionMiddleware }; diff --git a/server/http/routes.js b/server/http/routes.js index 46fb6d9d..acea503c 100644 --- a/server/http/routes.js +++ b/server/http/routes.js @@ -185,24 +185,22 @@ function registerApiRoutes(app) { app.get('/api/system/test/cli', requireAuth, async (req, res) => { const userId = req.session?.userId; const runtimeManager = req.app?.locals?.runtimeManager; - if (!runtimeManager || typeof runtimeManager.executeCommand !== 'function') { + if (!runtimeManager || typeof runtimeManager.executeCliCommand !== 'function') { return res.json({ passed: false, backendUsed: 'vm', detail: 'Runtime not configured on this server.' }); } - // Note: executeCommand always routes through the VM backend regardless of - // the cli_backend setting — desktop CLI routing is not yet implemented. try { - const result = await runtimeManager.executeCommand(userId, 'echo "cli_test_ok"', { timeout: 15000 }); + const result = await runtimeManager.executeCliCommand(userId, 'echo "cli_test_ok"', { timeout: 15000 }); const exitOk = result?.exitCode === 0; const outputOk = String(result?.stdout || '').includes('cli_test_ok'); return res.json({ passed: exitOk && outputOk, - backendUsed: 'vm', + backendUsed: result?.backend || 'unknown', detail: exitOk && outputOk ? 'Command executed successfully' : `Exit ${result?.exitCode ?? '?'}: ${String(result?.stderr || result?.stdout || '').slice(0, 120)}`, }); } catch (err) { - return res.json({ passed: false, backendUsed: 'vm', detail: String(err?.message || err).slice(0, 120) }); + return res.json({ passed: false, backendUsed: 'unknown', detail: String(err?.message || err).slice(0, 120) }); } }); diff --git a/server/routes/admin.js b/server/routes/admin.js index 6227cf87..87dc8f27 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -1025,7 +1025,7 @@ router.put('/api/config/rate-limits', requireAdminAuth, express.json(), (req, re router.get('/api/models', requireAdminAuth, async (req, res) => { const { getSupportedModels } = require('../services/ai/models'); try { - const models = await getSupportedModels(null, null); + const models = await getSupportedModels(null, null, { signal: req.signal }); const disabledStr = process.env.NEOAGENT_DISABLED_MODELS || ''; const disabledModels = disabledStr ? disabledStr.split(',').map(s => s.trim()).filter(Boolean) : []; res.json({ models, disabledModels }); diff --git a/server/routes/android.js b/server/routes/android.js index c24ca8ef..ae60cdc4 100644 --- a/server/routes/android.js +++ b/server/routes/android.js @@ -1,3 +1,5 @@ +'use strict'; + const express = require('express'); const fs = require('fs'); const path = require('path'); @@ -6,8 +8,7 @@ const router = express.Router(); const { DATA_DIR } = require('../../runtime/paths'); const { requireAuth } = require('../middleware/auth'); const { sanitizeError } = require('../utils/security'); -const { validateCloudUrl } = require('../utils/cloud-security'); -const { getRuntimeValidation } = require('../services/runtime/validation'); +const { validateAndroidIntentUrl } = require('../utils/cloud-security'); router.use(requireAuth); @@ -52,18 +53,6 @@ async function getAndroidController(req) { throw new Error('Android controller is unavailable.'); } -function getAndroidStatusSnapshot(req) { - const runtimeValidation = getRuntimeValidation(req.app?.locals?.runtimeManager); - const ready = Boolean(runtimeValidation?.ready); - return { - bootstrapped: false, - canBootstrap: ready, - devices: [], - lastStartError: ready ? null : (runtimeValidation?.issues?.[0] || 'VM runtime is not ready.'), - runtimeReady: ready, - }; -} - function handleAndroidAction(action) { return async (req, res) => { try { @@ -79,45 +68,49 @@ function handleAndroidAction(action) { router.get('/status', async (req, res) => { try { const controller = await getAndroidController(req); - res.json(await controller.getStatus().catch(() => getAndroidStatusSnapshot(req))); + res.json(await controller.getStatus({ signal: req.signal })); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); } }); router.post('/start', handleAndroidAction((controller, req) => - controller.requestStartEmulator(req.body || {}))); + controller.requestStartEmulator({ ...(req.body || {}), signal: req.signal }))); -router.post('/stop', handleAndroidAction((controller) => controller.stopEmulator())); +router.post('/stop', handleAndroidAction((controller, req) => controller.stopEmulator({ signal: req.signal }))); -router.get('/devices', handleAndroidAction(async (controller) => ({ - devices: await controller.listDevices(), +router.get('/devices', handleAndroidAction(async (controller, req) => ({ + devices: await controller.listDevices({ signal: req.signal }), }))); router.post('/screenshot', handleAndroidAction((controller, req) => - controller.screenshot(req.body || {}))); + controller.screenshot({ ...(req.body || {}), signal: req.signal }))); router.post('/observe', handleAndroidAction((controller, req) => - controller.observe(req.body || {}))); + controller.observe({ ...(req.body || {}), signal: req.signal }))); router.post('/ui-dump', handleAndroidAction((controller, req) => - controller.dumpUi(req.body || {}))); + controller.dumpUi({ ...(req.body || {}), signal: req.signal }))); router.get('/apps', handleAndroidAction((controller, req) => - controller.listApps({ includeSystem: req.query.includeSystem === 'true' }))); + controller.listApps({ includeSystem: req.query.includeSystem === 'true', signal: req.signal }))); router.post('/open-app', handleAndroidAction((controller, req) => - controller.openApp(req.body || {}))); + controller.openApp({ ...(req.body || {}), signal: req.signal }))); router.post('/open-intent', async (req, res) => { try { const body = req.body || {}; const intentUrl = body.data || body.url || body.uri; - if (intentUrl && typeof intentUrl === 'string' && !validateCloudUrl(intentUrl).allowed) { + if ( + intentUrl + && typeof intentUrl === 'string' + && !(await validateAndroidIntentUrl(intentUrl, { signal: req.signal })).allowed + ) { return res.status(403).json({ error: 'This URL is not permitted.' }); } const controller = await getAndroidController(req); - const result = await controller.openIntent(body); + const result = await controller.openIntent({ ...body, signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -125,22 +118,22 @@ router.post('/open-intent', async (req, res) => { }); router.post('/tap', handleAndroidAction((controller, req) => - controller.tap(req.body || {}))); + controller.tap({ ...(req.body || {}), signal: req.signal }))); router.post('/long-press', handleAndroidAction((controller, req) => - controller.longPress(req.body || {}))); + controller.longPress({ ...(req.body || {}), signal: req.signal }))); router.post('/type', handleAndroidAction((controller, req) => - controller.type(req.body || {}))); + controller.type({ ...(req.body || {}), signal: req.signal }))); router.post('/swipe', handleAndroidAction((controller, req) => - controller.swipe(req.body || {}))); + controller.swipe({ ...(req.body || {}), signal: req.signal }))); router.post('/press-key', handleAndroidAction((controller, req) => - controller.pressKey(req.body || {}))); + controller.pressKey({ ...(req.body || {}), signal: req.signal }))); router.post('/wait-for', handleAndroidAction((controller, req) => - controller.waitFor(req.body || {}))); + controller.waitFor({ ...(req.body || {}), signal: req.signal }))); router.post('/install-apk', (req, res) => { androidApkUpload.single('apk')(req, res, async (uploadError) => { @@ -162,7 +155,7 @@ router.post('/install-apk', (req, res) => { try { const controller = await getAndroidController(req); - const result = await controller.installApk({ apkPath: uploadedApkPath }); + const result = await controller.installApk({ apkPath: uploadedApkPath, signal: req.signal }); res.json({ ...result, filename: req.file.originalname, @@ -176,6 +169,9 @@ router.post('/install-apk', (req, res) => { }); }); -router.post('/shell', handleAndroidAction((controller, req) => controller.shell(req.body || {}))); +router.post('/shell', handleAndroidAction((controller, req) => controller.shell({ + ...(req.body || {}), + signal: req.signal, +}))); module.exports = router; diff --git a/server/routes/browser.js b/server/routes/browser.js index 8abbf112..b6aa82b9 100644 --- a/server/routes/browser.js +++ b/server/routes/browser.js @@ -1,8 +1,10 @@ +'use strict'; + const express = require('express'); const router = express.Router(); const { requireAuth } = require('../middleware/auth'); const { sanitizeError } = require('../utils/security'); -const { validateCloudUrl } = require('../utils/cloud-security'); +const { validateCloudUrlWithDns } = require('../utils/cloud-security'); const { getRuntimeValidation } = require('../services/runtime/validation'); router.use(requireAuth); @@ -11,7 +13,9 @@ async function getBrowserController(req) { const runtimeManager = req.app?.locals?.runtimeManager; if (runtimeManager && typeof runtimeManager.getBrowserProviderForUser === 'function') { const start = Date.now(); - const runtimeController = await runtimeManager.getBrowserProviderForUser(req.session?.userId); + const runtimeController = await runtimeManager.getBrowserProviderForUser(req.session?.userId, { + signal: req.signal, + }); const duration = Date.now() - start; if (duration > 1000) { console.log(`[HTTP] Browser controller acquired for user ${req.session?.userId} in ${duration}ms`); @@ -73,7 +77,7 @@ router.get('/cookies', async (req, res) => { if (typeof bc.getCookies !== 'function') { return res.status(501).json({ error: 'Cookie export is unavailable for this browser provider.' }); } - const result = await bc.getCookies(); + const result = await bc.getCookies({ signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -84,7 +88,7 @@ router.get('/cookies', async (req, res) => { router.post('/launch', async (req, res) => { try { const bc = await getBrowserController(req); - await bc.launch(req.body || {}); + await bc.launch({ ...(req.body || {}), signal: req.signal }); res.json({ success: true }); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -97,12 +101,12 @@ router.post('/navigate', async (req, res) => { const { url, waitFor } = req.body; if (!url) return res.status(400).json({ error: 'url required' }); - const bc = await getBrowserController(req); - - if (bc.providerType === 'vm' && !validateCloudUrl(url).allowed) { + const urlValidation = await validateCloudUrlWithDns(url, { signal: req.signal }); + if (!urlValidation.allowed) { return res.status(403).json({ error: 'This URL is not permitted.' }); } + const bc = await getBrowserController(req); const result = await bc.navigate(url, { waitUntil: waitFor || req.body?.waitUntil || 'domcontentloaded', waitFor, @@ -110,6 +114,7 @@ router.post('/navigate', async (req, res) => { fullPage: req.body?.fullPage === true, referrerMode: req.body?.referrerMode, challengeRetry: req.body?.challengeRetry, + signal: req.signal, }); res.json(result); } catch (err) { @@ -121,7 +126,7 @@ router.post('/navigate', async (req, res) => { router.post('/screenshot', async (req, res) => { try { const bc = await getBrowserController(req); - const result = await bc.screenshot(req.body || {}); + const result = await bc.screenshot({ ...(req.body || {}), signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -133,7 +138,7 @@ router.post('/click', async (req, res) => { try { const { selector, text } = req.body; const bc = await getBrowserController(req); - const result = await bc.click(selector, text, req.body?.screenshot !== false); + const result = await bc.click(selector, text, req.body?.screenshot !== false, { signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -147,7 +152,7 @@ router.post('/click-point', async (req, res) => { return res.status(400).json({ error: 'x and y required' }); } const bc = await getBrowserController(req); - const result = await bc.clickPoint(x, y, req.body?.screenshot !== false); + const result = await bc.clickPoint(x, y, req.body?.screenshot !== false, { signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -161,7 +166,7 @@ router.post('/mouse-move', async (req, res) => { return res.status(400).json({ error: 'x and y required' }); } const bc = await getBrowserController(req); - const result = await bc.hoverPoint(x, y, req.body || {}); + const result = await bc.hoverPoint(x, y, { ...(req.body || {}), signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -179,6 +184,7 @@ router.post('/fill', async (req, res) => { clear: req.body?.clear !== false, pressEnter: req.body?.pressEnter === true, screenshot: req.body?.screenshot !== false, + signal: req.signal, }); res.json(result); } catch (err) { @@ -193,6 +199,7 @@ router.post('/type-text', async (req, res) => { const result = await bc.typeText(String(text || ''), { pressEnter: req.body?.pressEnter === true, screenshot: req.body?.screenshot !== false, + signal: req.signal, }); res.json(result); } catch (err) { @@ -205,7 +212,7 @@ router.post('/press-key', async (req, res) => { const { key } = req.body || {}; if (!key) return res.status(400).json({ error: 'key required' }); const bc = await getBrowserController(req); - const result = await bc.pressKey(key, req.body?.screenshot !== false); + const result = await bc.pressKey(key, req.body?.screenshot !== false, { signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -219,6 +226,7 @@ router.post('/scroll', async (req, res) => { req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false, + { signal: req.signal }, ); res.json(result); } catch (err) { @@ -230,7 +238,7 @@ router.post('/scroll', async (req, res) => { router.post('/extract', async (req, res) => { try { const bc = await getBrowserController(req); - const result = await bc.extractContent(req.body || {}); + const result = await bc.extractContent({ ...(req.body || {}), signal: req.signal }); res.json(result); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -252,7 +260,7 @@ router.post('/execute', async (req, res) => { } const bc = await getBrowserController(req); - const result = await bc.executeJS(code); + const result = await bc.executeJS(code, { signal: req.signal }); res.json({ result }); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); @@ -263,7 +271,7 @@ router.post('/execute', async (req, res) => { router.post('/close', async (req, res) => { try { const bc = await getBrowserController(req); - await bc.closeBrowser(); + await bc.closeBrowser({ signal: req.signal }); res.json({ success: true }); } catch (err) { res.status(500).json({ error: sanitizeError(err) }); diff --git a/server/routes/desktop.js b/server/routes/desktop.js index 33b0f032..65c70d7f 100644 --- a/server/routes/desktop.js +++ b/server/routes/desktop.js @@ -1,3 +1,5 @@ +'use strict'; + const express = require('express'); const router = express.Router(); const { requireAuth } = require('../middleware/auth'); @@ -144,6 +146,7 @@ router.post('/screenshot', (req, res) => handleDesktopAction(req, res, (provider, request) => provider.screenshot({ ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), + signal: request.signal, }))); router.post('/observe', (req, res) => @@ -151,6 +154,7 @@ router.post('/observe', (req, res) => ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), includeTree: parseOptionalBoolean(request.body?.includeTree, false), + signal: request.signal, }))); router.post('/click', (req, res) => @@ -160,6 +164,7 @@ router.post('/click', (req, res) => { ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), + signal: request.signal, }, ))); @@ -170,6 +175,7 @@ router.post('/mouse-move', (req, res) => { ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), + signal: request.signal, }, ))); @@ -182,6 +188,7 @@ router.post('/drag', (req, res) => x2: parseFiniteNumber(request.body?.x2, 'x2', { min: -100000, max: 100000 }), y2: parseFiniteNumber(request.body?.y2, 'y2', { min: -100000, max: 100000 }), durationMs: Math.round(parseFiniteNumber(request.body?.durationMs ?? 280, 'durationMs', { min: 20, max: 5000 })), + signal: request.signal, }))); router.post('/scroll', (req, res) => @@ -190,6 +197,7 @@ router.post('/scroll', (req, res) => deviceId: parseDeviceId(request.body?.deviceId), deltaX: Math.round(parseFiniteNumber(request.body?.deltaX ?? 0, 'deltaX', { min: -5000, max: 5000 })), deltaY: Math.round(parseFiniteNumber(request.body?.deltaY ?? 0, 'deltaY', { min: -5000, max: 5000 })), + signal: request.signal, }))); router.post('/type-text', (req, res) => @@ -199,6 +207,7 @@ router.post('/type-text', (req, res) => ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), pressEnter: parseOptionalBoolean(request.body?.pressEnter, false), + signal: request.signal, }, ))); @@ -208,6 +217,7 @@ router.post('/press-key', (req, res) => { ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), + signal: request.signal, }, ))); @@ -216,11 +226,15 @@ router.post('/launch-app', (req, res) => ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), app: parseRequiredString(request.body?.app, 'app', MAX_APP_LENGTH), + signal: request.signal, }))); router.get('/displays', (req, res) => handleDesktopAction(req, res, (provider, request) => - provider.listDisplays(parseTypedDisplayQuery(request.query)))); + provider.listDisplays({ + ...parseTypedDisplayQuery(request.query), + signal: request.signal, + }))); router.post('/select-display', (req, res) => handleDesktopAction(req, res, (provider, request) => provider.selectDisplay( @@ -228,6 +242,7 @@ router.post('/select-display', (req, res) => { ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), + signal: request.signal, }, ))); @@ -239,12 +254,14 @@ router.post('/pause-device', (req, res) => handleDesktopAction(req, res, (provider, request) => provider.pauseDevice( parseRequiredString(request.body?.deviceId, 'deviceId', 256), parseOptionalBoolean(request.body?.paused, true), + { signal: request.signal }, ))); router.post('/tree', (req, res) => handleDesktopAction(req, res, (provider, request) => provider.getAccessibilityTree({ ...(request.body || {}), deviceId: parseDeviceId(request.body?.deviceId), + signal: request.signal, }))); module.exports = router; diff --git a/server/routes/integrations.js b/server/routes/integrations.js index e4aa5ceb..8961e6a6 100644 --- a/server/routes/integrations.js +++ b/server/routes/integrations.js @@ -101,7 +101,7 @@ router.get('/oauth/callback', async (req, res) => { if (!manager) { throw new Error('Official integration manager is not available on app.locals.integrationManager.'); } - const result = await manager.finishOAuth(state, code); + const result = await manager.finishOAuth(state, code, { signal: req.signal }); const payload = JSON.stringify({ type: 'integration_oauth_success', provider: result.provider, @@ -320,6 +320,7 @@ router.post('/:provider/connect', async (req, res) => { { appKey: req.body?.appId, agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)), + signal: req.signal, }, ); res.json(result); @@ -340,6 +341,7 @@ router.post('/:provider/disconnect', async (req, res) => { { connectionId: req.body?.connectionId, agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)), + signal: req.signal, }, ); res.json(result); @@ -429,6 +431,7 @@ router.put('/:provider/config', async (req, res) => { userId: req.session.userId, agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)), config: req.body?.config || req.body || {}, + signal: req.signal, }); res.json({ provider: provider.key, @@ -458,6 +461,7 @@ router.delete('/:provider/config', async (req, res) => { await provider.clearUserConfig({ userId: req.session.userId, agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)), + signal: req.signal, }); res.json({ provider: provider.key, diff --git a/server/routes/memory.js b/server/routes/memory.js index 674c8fb7..eba21c0c 100644 --- a/server/routes/memory.js +++ b/server/routes/memory.js @@ -198,6 +198,7 @@ router.post('/ingestion/documents', async (req, res) => { connectionId: req.body.connectionId, sourceAccount: req.body.sourceAccount, metadata: normalizeMetadata(req.body.metadata), + signal: req.signal, }); res.json(result); } catch (err) { diff --git a/server/routes/settings.js b/server/routes/settings.js index 87767392..00d2af23 100644 --- a/server/routes/settings.js +++ b/server/routes/settings.js @@ -40,6 +40,14 @@ const AGENT_SETTING_KEYS = new Set([ 'cost_mode', 'chat_history_window', 'tool_replay_budget_chars', + 'tool_replay_budget_file_chars', + 'tool_replay_budget_browser_chars', + 'tool_replay_budget_command_chars', + 'max_iterations', + 'max_consecutive_read_only_iterations', + 'max_consecutive_tool_failures', + 'max_model_failure_recoveries', + 'compaction_threshold', 'subagent_max_iterations', 'subagent_max_children_per_run', 'assistant_behavior_notes', @@ -194,16 +202,19 @@ async function resetEnvBackedSettingValue(req, key) { // Get supported models metadata router.get('/meta/models', async (req, res) => { const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req)); - const models = await getSupportedModels(req.session.userId, agentId); + const models = await getSupportedModels(req.session.userId, agentId, { signal: req.signal }); res.json({ models }); }); router.get('/meta/ai-providers', async (req, res) => { const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req)); - const [providers, models] = await Promise.all([ - getProviderHealthCatalog(req.session.userId, agentId), - getSupportedModels(req.session.userId, agentId), - ]); + const providers = await getProviderHealthCatalog(req.session.userId, agentId, { + signal: req.signal, + }); + const models = await getSupportedModels(req.session.userId, agentId, { + providerCatalog: providers, + signal: req.signal, + }); const modelCounts = models.reduce((acc, model) => { acc[model.provider] = (acc[model.provider] || 0) + 1; diff --git a/server/routes/social_reach.js b/server/routes/social_reach.js index a6df9291..1e2abb32 100644 --- a/server/routes/social_reach.js +++ b/server/routes/social_reach.js @@ -25,7 +25,7 @@ router.get('/status', async (req, res) => { if (!service || typeof service.getStatus !== 'function') { return res.status(503).json({ error: 'Social reach service is unavailable.' }); } - return res.json(await service.getStatus(req.session.userId)); + return res.json(await service.getStatus(req.session.userId, { signal: req.signal })); } catch (error) { return sendError(res, error); } @@ -37,7 +37,11 @@ router.post('/read', async (req, res) => { if (!service || typeof service.read !== 'function') { return res.status(503).json({ error: 'Social reach service is unavailable.' }); } - return res.json(await service.read(req.session.userId, req.body || {})); + return res.json(await service.read( + req.session.userId, + req.body || {}, + { signal: req.signal }, + )); } catch (error) { return sendError(res, error); } @@ -49,7 +53,11 @@ router.post('/search', async (req, res) => { if (!service || typeof service.search !== 'function') { return res.status(503).json({ error: 'Social reach service is unavailable.' }); } - return res.json(await service.search(req.session.userId, req.body || {})); + return res.json(await service.search( + req.session.userId, + req.body || {}, + { signal: req.signal }, + )); } catch (error) { return sendError(res, error); } @@ -67,6 +75,7 @@ router.post('/cookies/import', async (req, res) => { } return res.json(await service.importCookiesFromExtension(req.session.userId, platform, { tokenId: req.body?.tokenId || req.body?.token_id || null, + signal: req.signal, })); } catch (error) { return sendError(res, error); diff --git a/server/routes/social_video.js b/server/routes/social_video.js index 92fa103c..59999e31 100644 --- a/server/routes/social_video.js +++ b/server/routes/social_video.js @@ -1,3 +1,5 @@ +'use strict'; + const express = require('express'); const { requireAuth } = require('../middleware/auth'); const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager'); @@ -18,6 +20,7 @@ router.get('/health', async (req, res) => { } const health = await service.getHealthStatus({ forceRefresh: req.query?.refresh === '1' || req.query?.refresh === 'true', + signal: req.signal, }); return res.json(health); } catch (error) { @@ -46,6 +49,7 @@ router.post('/extract', async (req, res) => { includeFrame: req.body?.include_frame !== false, forceStt: req.body?.force_stt === true, agentId, + signal: req.signal, }); if (result?.setup?.ready === false) { diff --git a/server/services/ai/compaction.js b/server/services/ai/compaction.js index 5e68c574..5ea73f56 100644 --- a/server/services/ai/compaction.js +++ b/server/services/ai/compaction.js @@ -1,4 +1,4 @@ -async function compact(messages, provider, model, contextWindow = null) { +async function compact(messages, provider, model, contextWindow = null, options = {}) { const systemMsg = messages.find(m => m.role === 'system'); const nonSystem = messages.filter(m => m.role !== 'system'); const beforeTokens = estimateTokenCount(messages); @@ -26,7 +26,11 @@ async function compact(messages, provider, model, contextWindow = null) { ]; try { - const response = await provider.chat(summaryPrompt, [], { model, maxTokens: 1600 }); + const response = await provider.chat(summaryPrompt, [], { + model, + maxTokens: 1600, + signal: options.signal, + }); const summary = response.content || 'Previous conversation context (summary unavailable).'; const compactedMessages = []; @@ -44,6 +48,7 @@ async function compact(messages, provider, model, contextWindow = null) { } return compactedMessages; } catch (err) { + if (options.signal?.aborted) throw err; console.error('Compaction failed:', err.message); const trimmed = []; if (systemMsg) trimmed.push(systemMsg); diff --git a/server/services/ai/history.js b/server/services/ai/history.js index fcc84897..6141b277 100644 --- a/server/services/ai/history.js +++ b/server/services/ai/history.js @@ -1,5 +1,8 @@ +'use strict'; + const db = require('../../db/database'); const { resolveAgentId } = require('../agents/manager'); +const { throwIfAborted } = require('../../utils/abort'); const WEB_SUMMARY_KEY = 'web_chat_summary'; const WEB_SUMMARY_COUNT_KEY = 'web_chat_summary_count'; @@ -109,8 +112,16 @@ function serializeHistoryForSummary(messages) { }).join('\n'); } -async function summarizeMessages(provider, model, existingSummary, messages, label = 'conversation') { +async function summarizeMessages( + provider, + model, + existingSummary, + messages, + label = 'conversation', + options = {}, +) { if (!messages.length) return existingSummary || ''; + throwIfAborted(options.signal, 'Conversation summary refresh aborted.'); const prompt = [ { @@ -127,7 +138,12 @@ async function summarizeMessages(provider, model, existingSummary, messages, lab } ]; - const response = await provider.chat(prompt, [], { model, maxTokens: 900 }); + const response = await provider.chat(prompt, [], { + model, + maxTokens: 900, + signal: options.signal, + }); + throwIfAborted(options.signal, 'Conversation summary refresh aborted.'); return clampSummary(response.content || existingSummary || ''); } @@ -183,7 +199,15 @@ async function refreshWebChatSummary(userId, provider, model, recentLimit, force 'SELECT role, content FROM conversation_history WHERE user_id = ? AND agent_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?' ).all(userId, agentId, newMessages, count); - const nextSummary = clampSummary(await summarizeMessages(provider, model, summary, normalizeHistoryRows(rows), 'web chat')); + const nextSummary = clampSummary(await summarizeMessages( + provider, + model, + summary, + normalizeHistoryRows(rows), + 'web chat', + options, + )); + throwIfAborted(options.signal, 'Web chat summary refresh aborted.'); const upsert = db.prepare( 'INSERT INTO agent_settings (user_id, agent_id, key, value) VALUES (?, ?, ?, ?) ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value' ); @@ -214,7 +238,14 @@ function getConversationContext(conversationId, recentLimit) { }; } -async function refreshConversationSummary(conversationId, provider, model, recentLimit, force = false) { +async function refreshConversationSummary( + conversationId, + provider, + model, + recentLimit, + force = false, + options = {}, +) { const convo = db.prepare( 'SELECT summary, summary_message_count FROM conversations WHERE id = ?' ).get(conversationId); @@ -233,7 +264,15 @@ async function refreshConversationSummary(conversationId, provider, model, recen 'SELECT role, content, tool_calls, tool_call_id, name FROM conversation_messages WHERE conversation_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?' ).all(conversationId, newMessages, currentCount); - const nextSummary = clampSummary(await summarizeMessages(provider, model, convo.summary || '', normalizeHistoryRows(rows), 'thread')); + const nextSummary = clampSummary(await summarizeMessages( + provider, + model, + convo.summary || '', + normalizeHistoryRows(rows), + 'thread', + options, + )); + throwIfAborted(options.signal, 'Conversation summary refresh aborted.'); db.prepare( "UPDATE conversations SET summary = ?, summary_message_count = ?, last_summary = datetime('now') WHERE id = ?" ).run(nextSummary, targetCount, conversationId); diff --git a/server/services/ai/integrated_tools/http_request.js b/server/services/ai/integrated_tools/http_request.js new file mode 100644 index 00000000..bb59d22e --- /dev/null +++ b/server/services/ai/integrated_tools/http_request.js @@ -0,0 +1,8 @@ +'use strict'; + +const safeRequest = require('../../network/safe_request'); + +module.exports = { + ...safeRequest, + executeHttpRequest: safeRequest.executeSafeHttpRequest, +}; diff --git a/server/services/ai/loop/agent_engine_core.js b/server/services/ai/loop/agent_engine_core.js index 220050e2..888764b6 100644 --- a/server/services/ai/loop/agent_engine_core.js +++ b/server/services/ai/loop/agent_engine_core.js @@ -19,8 +19,10 @@ const { const { summarizeCapabilityHealth } = require('../capabilityHealth'); const { shouldAcceptTaskComplete } = require('../completion'); const { shortenRunId, summarizeForLog } = require('../logFormat'); +const { getProviderForUser } = require('../provider_selector'); const { runConversation } = require('./conversation_loop'); const { + TERMINAL_STATUSES, checkpointRun, closeRun, getRunControl, @@ -30,6 +32,7 @@ const { const { buildChurnAssessmentPrompt, buildCompletionDecisionPrompt, + enforceTerminalReplyDecision, normalizeChurnAssessment, normalizeCompletionDecision, resolveRunGoalContext, @@ -67,7 +70,7 @@ const { const { requestModelResponse: requestModelResponseImpl, requestStructuredJson: requestStructuredJsonImpl, - withModelCallTimeout, + runAbortableModelCall, } = require('./model_io'); const { publishInterimUpdate: publishInterimUpdateImpl, @@ -82,6 +85,7 @@ const { normalizeOutgoingMessage, clampRunContext, } = require('../messagingFallback'); +const { isDeferredWorkReply } = require('../terminal_reply'); const { summarizeToolExecutions, } = require('../toolEvidence'); @@ -97,6 +101,13 @@ const { normalizeRetrievalPlan, shouldEnhanceRetrieval, } = require('../../memory/retrieval_reasoning'); +const { + createAbortError, + createLinkedAbortController, + isAbortError, + runWithAbortTimeout, + throwIfAborted, +} = require('../../../utils/abort'); function buildInitialRunMetadata(options = {}) { const metadata = {}; @@ -106,6 +117,9 @@ function buildInitialRunMetadata(options = {}) { if (options.widgetId != null && String(options.widgetId).trim()) { metadata.widgetId = options.widgetId; } + if (options.messagingInboundJobId != null && String(options.messagingInboundJobId).trim()) { + metadata.messagingInboundJobId = String(options.messagingInboundJobId).trim(); + } return metadata; } @@ -131,99 +145,6 @@ function buildAnalyzeTaskFallback(forceMode, userMessage = '') { }; } -async function getProviderForUser(userId, task = '', isSubagent = false, modelOverride = null, providerConfig = {}) { - const { getSupportedModels, createProviderInstance } = require('../models'); - const agentId = providerConfig.agentId || null; - const aiSettings = getAiSettings(userId, agentId); - const models = await getSupportedModels(userId, agentId); - - let enabledIds = Array.isArray(aiSettings.enabled_models) ? aiSettings.enabled_models : []; - const defaultChatModel = aiSettings.default_chat_model || 'auto'; - const defaultSubagentModel = aiSettings.default_subagent_model || 'auto'; - const smarterSelection = aiSettings.smarter_model_selector !== false && aiSettings.smarter_model_selector !== 'false'; - - const knownModelIds = new Set(models.map((m) => m.id)); - const selectableModels = models.filter((m) => m.available !== false); - - enabledIds = Array.isArray(enabledIds) - ? enabledIds - .map((id) => String(id)) - .filter((id) => knownModelIds.has(id)) - : []; - - let availableModels = selectableModels.filter((m) => enabledIds.includes(m.id)); - if (availableModels.length === 0) { - enabledIds = selectableModels.map((m) => m.id); - availableModels = [...selectableModels]; - } - - const fallbackModel = availableModels.length > 0 ? availableModels[0] : selectableModels[0]; - - if (!fallbackModel) { - throw new Error('No AI providers are currently available. Open Settings and configure at least one provider.'); - } - - let selectedModelDef = fallbackModel; - const userSelectedDefault = isSubagent ? defaultSubagentModel : defaultChatModel; - - if (modelOverride && typeof modelOverride === 'string') { - const requested = models.find((m) => m.id === modelOverride.trim()); - if (requested && requested.available !== false && enabledIds.includes(requested.id)) { - selectedModelDef = requested; - return { - provider: createProviderInstance(selectedModelDef.provider, userId, providerConfig), - model: selectedModelDef.id, - providerName: selectedModelDef.provider - }; - } - } - - if (userSelectedDefault && userSelectedDefault !== 'auto') { - selectedModelDef = models.find((m) => m.id === userSelectedDefault) || fallbackModel; - } else { - const selectionHint = providerConfig.selectionHint && typeof providerConfig.selectionHint === 'object' - ? providerConfig.selectionHint - : {}; - const preferredPurpose = String(selectionHint.purpose || '').trim().toLowerCase(); - const highAutonomy = selectionHint.autonomyLevel === 'high' || selectionHint.complexity === 'complex'; - const requiredConfidence = String(selectionHint.requiredConfidence || '').trim().toLowerCase(); - const costMode = String(selectionHint.costMode || aiSettings.cost_mode || 'balanced_auto').trim().toLowerCase(); - const requestedPurpose = ['planning', 'coding', 'general', 'fast'].includes(preferredPurpose) - ? preferredPurpose - : ''; - const priceRank = { free: 0, cheap: 1, medium: 2, expensive: 3 }; - const chooseForPurpose = (purpose) => { - const candidates = availableModels.filter((model) => model.purpose === purpose); - if (candidates.length === 0) return null; - if (['economy', 'cost_saver', 'lowest_cost'].includes(costMode)) { - return [...candidates].sort((left, right) => ( - (priceRank[left.priceTier] ?? 99) - (priceRank[right.priceTier] ?? 99) - ))[0]; - } - if (['quality', 'highest_quality'].includes(costMode) || requiredConfidence === 'high') { - return candidates.find((model) => model.priceTier !== 'free' && model.priceTier !== 'cheap') || candidates[0]; - } - return candidates[0]; - }; - - if (smarterSelection && requestedPurpose) { - selectedModelDef = chooseForPurpose(requestedPurpose) || fallbackModel; - } else if (smarterSelection && highAutonomy) { - selectedModelDef = chooseForPurpose('planning') || chooseForPurpose('general') || fallbackModel; - } else if (isSubagent) { - selectedModelDef = chooseForPurpose('fast') || fallbackModel; - } else { - selectedModelDef = chooseForPurpose('general') || fallbackModel; - } - } - - return { - provider: createProviderInstance(selectedModelDef.provider, userId, providerConfig), - model: selectedModelDef.id, - providerName: selectedModelDef.provider - }; -} - function estimateTokenValue(value) { if (!value) return 0; if (typeof value === 'string') return Math.ceil(value.length / 4); @@ -234,6 +155,13 @@ class AgentEngine { constructor(io, services = {}) { this.io = io; this.activeRuns = new Map(); + this.activeRunPromises = new Set(); + this.backgroundTasks = new Set(); + this.backgroundTaskQueues = new Map(); + this.subagentStartupTasks = new Set(); + this.lifecycleAbortController = new AbortController(); + this.shuttingDown = false; + this.shutdownPromise = null; this.subagents = new Map(); this.app = services.app || null; this.browserController = services.browserController || null; @@ -249,6 +177,107 @@ class AgentEngine { this.messagingDeliveryRetry = services.messagingDeliveryRetry || {}; } + trackBackgroundTask(task, options = {}) { + if (typeof task !== 'function') { + throw new TypeError('Background task must be a function.'); + } + if (this.shuttingDown || this.lifecycleAbortController.signal.aborted) { + return Promise.reject(createAbortError( + this.lifecycleAbortController.signal, + 'Agent engine is shutting down.', + )); + } + + const key = String(options.key || '').trim(); + const previous = key ? this.backgroundTaskQueues.get(key) : null; + if (previous && options.coalesce === true) return previous; + + const linked = createLinkedAbortController([ + this.lifecycleAbortController.signal, + options.signal, + ]); + const tracked = Promise.resolve().then(async () => { + if (previous) { + try { + await previous; + } catch { + // A failed predecessor must not permanently poison this queue. + } + } + throwIfAborted(linked.signal, 'Background task aborted.'); + return task(linked.signal); + }).finally(() => { + linked.cleanup(); + }); + + this.backgroundTasks.add(tracked); + if (key) this.backgroundTaskQueues.set(key, tracked); + const cleanup = () => { + this.backgroundTasks.delete(tracked); + if (key && this.backgroundTaskQueues.get(key) === tracked) { + this.backgroundTaskQueues.delete(key); + } + }; + tracked.then(cleanup, cleanup); + return tracked; + } + + shutdown(options = {}) { + if (this.shutdownPromise) return this.shutdownPromise; + + const reason = String( + options.reason || 'Server shutting down while agent work was in progress.', + ); + const timeoutMs = Math.max(100, Number(options.timeoutMs) || 10000); + this.shuttingDown = true; + this.lifecycleAbortController.abort(reason); + this.interruptAllActiveRuns(reason); + + const childShutdowns = []; + const childPromises = []; + for (const record of this.subagents.values()) { + if (typeof record.engine?.shutdown === 'function') { + childShutdowns.push(record.engine.shutdown({ reason, timeoutMs })); + } + if (record.promise) childPromises.push(record.promise); + } + const pending = [ + ...this.activeRunPromises, + ...this.backgroundTasks, + ...this.subagentStartupTasks, + ...childShutdowns, + ...childPromises, + ]; + + this.shutdownPromise = (async () => { + if (pending.length === 0) { + return { state: 'stopped', timedOut: false, pendingCount: 0 }; + } + + let timeout = null; + const timeoutResult = Symbol('agent-engine-shutdown-timeout'); + const result = await Promise.race([ + Promise.allSettled(pending), + new Promise((resolve) => { + timeout = setTimeout(() => resolve(timeoutResult), timeoutMs); + }), + ]); + if (timeout) clearTimeout(timeout); + const timedOut = result === timeoutResult; + return { + state: timedOut ? 'timeout' : 'stopped', + timedOut, + pendingCount: timedOut + ? this.activeRunPromises.size + + this.backgroundTasks.size + + this.subagentStartupTasks.size + + Array.from(this.subagents.values()).filter((record) => !record.settled).length + : 0, + }; + })(); + return this.shutdownPromise; + } + async buildSystemPrompt(userId, context = {}) { const { buildSystemPromptSections } = require('../systemPrompt'); const { MemoryManager } = require('../../memory/manager'); @@ -287,18 +316,34 @@ class AgentEngine { options = {}, returnDetails = false, }) { - const initial = await memoryManager.recallMemory(userId, query, 12, { agentId }); + const signal = options.signal || this.getRunMeta(runId)?.abortController?.signal || null; + const initial = await memoryManager.recallMemory(userId, query, 12, { + agentId, + signal, + }); const pendingChunks = memoryManager.getPendingExtractionChunks?.(userId, agentId, 5) || []; if (pendingChunks.length) { - this.extractPendingChunks(pendingChunks, { - userId, - agentId, - provider, - providerName, - model, - memoryManager, - }).catch((err) => console.warn('[Memory] Background chunk extraction failed:', err.message)); + this.trackBackgroundTask((backgroundSignal) => this.extractPendingChunks( + pendingChunks, + { + userId, + agentId, + provider, + providerName, + model, + memoryManager, + signal: backgroundSignal, + }, + ), { + key: `memory-extraction:${userId}:${agentId || 'main'}`, + coalesce: true, + signal, + }).catch((err) => { + if (!isAbortError(err, signal) && !this.shuttingDown) { + console.warn('[Memory] Background chunk extraction failed:', err.message); + } + }); } const decision = shouldEnhanceRetrieval(initial); @@ -335,7 +380,7 @@ class AgentEngine { normalize: (raw) => normalizeRetrievalPlan(raw, query), fallback: normalizeRetrievalPlan({}, query), reasoningEffort: this.getReasoningEffort(providerName, options), - telemetry: { runId, stepId, userId, agentId }, + telemetry: { runId, stepId, userId, agentId, signal }, phase: 'memory_retrieval_plan', }); plan = planned.value; @@ -346,6 +391,7 @@ class AgentEngine { agentId, validAt: plan.validAt, includeHistory: plan.temporalMode === 'historical', + signal, })); } merged = mergeRetrievalResults(resultSets, 30); @@ -360,7 +406,7 @@ class AgentEngine { normalize: (raw) => normalizeRerankResult(raw, merged), fallback: merged, reasoningEffort: this.getReasoningEffort(providerName, options), - telemetry: { runId, stepId, userId, agentId }, + telemetry: { runId, stepId, userId, agentId, signal }, phase: 'memory_retrieval_rerank', }); reranked = rerankResponse.value; @@ -368,6 +414,7 @@ class AgentEngine { reranked = merged; } } catch (error) { + if (isAbortError(error, signal)) throw error; console.warn('[Memory] Retrieval enhancement failed:', error.message); plan = null; merged = initial; @@ -406,10 +453,8 @@ class AgentEngine { providerName, model, memoryManager, + signal = null, }) { - const ids = chunks.map((c) => c.id); - memoryManager.markChunksExtracted?.(ids, { success: true }); - const consolidationSchema = JSON.stringify({ memory_candidates: [{ memory: 'Concise standalone fact.', @@ -430,6 +475,7 @@ class AgentEngine { for (const chunk of chunks) { try { + throwIfAborted(signal, 'Memory extraction aborted.'); const result = await this.requestStructuredJson({ provider, providerName, @@ -446,6 +492,7 @@ class AgentEngine { maxTokens: 800, normalize: (raw) => normalizeMemoryCandidates(raw?.memory_candidates || []), fallback: [], + telemetry: { userId, agentId, signal }, phase: 'document_extraction', }); @@ -457,10 +504,13 @@ class AgentEngine { trustLevel: 'external_source', sourceChunkMemoryId: chunk.id, }, + signal, }); } + memoryManager.markChunksExtracted?.([chunk.id], { success: true }); } catch (err) { memoryManager.markChunksExtracted?.([chunk.id], { success: false }); + if (isAbortError(err, signal)) throw err; console.warn('[Memory] Document chunk extraction failed:', err.message); } } @@ -523,6 +573,7 @@ class AgentEngine { ? deliverableResult.artifacts.slice(0, 6) : [], }, + signal: this.getRunMeta(runId)?.abortController?.signal || null, }, ); } catch (error) { @@ -766,7 +817,7 @@ class AgentEngine { phase: 'completion_decision', }); return { - decision: response.value, + decision: enforceTerminalReplyDecision(response.value, lastReply), usage: response.usage, raw: response.raw, }; @@ -931,11 +982,12 @@ class AgentEngine { } ]; - const response = await withModelCallTimeout( - provider.chat(promptMessages, [], { + const response = await runAbortableModelCall( + (signal) => provider.chat(promptMessages, [], { model, maxTokens: 800, reasoningEffort: this.getReasoningEffort(providerName, options), + signal, }), options, 'Conversation state refresh', @@ -966,6 +1018,7 @@ class AgentEngine { agentId: options.agentId || null, conversationId, runId, + signal: options.signal, }, ); const { invalidateSystemPromptCache } = require('../systemPrompt'); @@ -982,8 +1035,8 @@ class AgentEngine { return executeToolImpl(this, toolName, args, context); } - isReadOnlyToolCall(toolCall) { - return isReadOnlyToolCallImpl(this, toolCall); + isReadOnlyToolCall(toolCall, toolDefinition = null) { + return isReadOnlyToolCallImpl(toolCall, toolDefinition); } async executeReadOnlyBatch(toolCalls, context = {}) { @@ -1172,7 +1225,8 @@ class AgentEngine { && toolExecutions.length === 0 && failedStepCount === 0 && !messagingSent - && Boolean(String(lastReply || '').trim()); + && Boolean(String(lastReply || '').trim()) + && !isDeferredWorkReply(lastReply); } getMessagingRetryLimit(maxIterations) { @@ -1291,15 +1345,37 @@ class AgentEngine { } async runWithModel(userId, userMessage, options = {}, _modelOverride = null) { - return runConversation(this, userId, userMessage, options, _modelOverride); - } - - async _runWithModelInternal(userId, userMessage, options = {}, _modelOverride = null) { - return runConversation(this, userId, userMessage, options, _modelOverride); + if (this.shuttingDown || this.lifecycleAbortController.signal.aborted) { + const error = createAbortError( + this.lifecycleAbortController.signal, + 'Agent engine is shutting down.', + ); + error.code = error.code || 'AGENT_ENGINE_SHUTTING_DOWN'; + throw error; + } + const runPromise = runConversation(this, userId, userMessage, options, _modelOverride); + this.activeRunPromises.add(runPromise); + try { + return await runPromise; + } finally { + this.activeRunPromises.delete(runPromise); + } } async spawnSubagent(userId, parentRunId, task, options = {}) { - const parentRunMeta = this.getRunMeta(parentRunId); + if (this.shuttingDown || this.lifecycleAbortController.signal.aborted) { + throw createAbortError(this.lifecycleAbortController.signal, 'Agent engine is shutting down.'); + } + + let parentRunMeta = this.getRunMeta(parentRunId); + if ( + !parentRunMeta + || parentRunMeta.userId !== userId + || parentRunMeta.status !== 'running' + || parentRunMeta.aborted === true + ) { + return { error: 'The parent run is no longer active, so a sub-agent cannot be started.' }; + } const parentDepth = Math.max(0, Number(parentRunMeta?.subagentDepth) || 0); if (parentDepth >= 1) { return { @@ -1309,24 +1385,77 @@ class AgentEngine { const aiSettings = getAiSettings(userId, options.agentId || null); const maxSubagentsPerRun = Math.max(1, Number(aiSettings.subagent_max_children_per_run) || 10); - const existingSubagents = Array.from(this.subagents.values()) - .filter((record) => record.parentRunId === parentRunId); - if (existingSubagents.length >= maxSubagentsPerRun) { + const countExistingSubagents = () => Array.from(this.subagents.values()) + .filter((record) => record.parentRunId === parentRunId).length; + if (countExistingSubagents() >= maxSubagentsPerRun) { return { - error: `This run has already spawned ${existingSubagents.length} sub-agents. The limit for one run is ${maxSubagentsPerRun}.`, + error: `This run has already spawned ${countExistingSubagents()} sub-agents. The limit for one run is ${maxSubagentsPerRun}.`, }; } const handle = uuidv4(); const childRunId = uuidv4(); let relevantMemories = []; + const startupLink = createLinkedAbortController([ + this.lifecycleAbortController.signal, + parentRunMeta.abortController?.signal, + options.signal, + ]); + let memoryRecall = null; try { - relevantMemories = this.memoryManager - ? await this.memoryManager.recallMemory(userId, task, 4, { - agentId: options.agentId || null, - }) - : []; - } catch {} + throwIfAborted(startupLink.signal, 'Sub-agent startup was aborted.'); + memoryRecall = this.memoryManager + ? runWithAbortTimeout( + (signal) => this.memoryManager.recallMemory(userId, task, 4, { + agentId: options.agentId || null, + signal, + }), + { + label: 'Sub-agent memory recall', + signal: startupLink.signal, + }, + ) + : Promise.resolve([]); + this.subagentStartupTasks.add(memoryRecall); + relevantMemories = await memoryRecall; + throwIfAborted(startupLink.signal, 'Sub-agent startup was aborted.'); + } catch (error) { + if (isAbortError(error, startupLink.signal)) throw error; + console.warn('[AgentEngine] Sub-agent memory recall failed:', error?.message || error); + } finally { + if (memoryRecall) this.subagentStartupTasks.delete(memoryRecall); + startupLink.cleanup(); + } + + parentRunMeta = this.getRunMeta(parentRunId); + if ( + this.shuttingDown + || this.lifecycleAbortController.signal.aborted + || !parentRunMeta + || parentRunMeta.userId !== userId + || parentRunMeta.status !== 'running' + || parentRunMeta.aborted === true + ) { + throw createAbortError( + this.lifecycleAbortController.signal.aborted + ? this.lifecycleAbortController.signal + : parentRunMeta?.abortController?.signal, + 'The parent run stopped before the sub-agent could start.', + ); + } + const currentSubagentCount = countExistingSubagents(); + if (currentSubagentCount >= maxSubagentsPerRun) { + return { + error: `This run has already spawned ${currentSubagentCount} sub-agents. The limit for one run is ${maxSubagentsPerRun}.`, + }; + } + + const childLink = createLinkedAbortController([ + this.lifecycleAbortController.signal, + parentRunMeta.abortController?.signal, + options.signal, + ]); + throwIfAborted(childLink.signal, 'Sub-agent startup was aborted.'); const subEngine = new AgentEngine(this.io, { app: options.app || this.app, browserController: this.browserController, @@ -1372,6 +1501,8 @@ class AgentEngine { error: null, engine: subEngine, promise: null, + settled: false, + deleteWhenSettled: false, }; this.subagents.set(handle, record); this.emit(userId, 'run:subagent', { @@ -1395,9 +1526,11 @@ class AgentEngine { agentId: options.agentId || null, subagentDepth: parentDepth + 1, disallowedToolNames: ['spawn_subagent'], + signal: childLink.signal, }, options.model || null ); + if (record.status === 'cancelled' || childLink.signal.aborted) return record; record.status = result.status || 'completed'; let structured = null; try { @@ -1425,16 +1558,23 @@ class AgentEngine { }); return record; } catch (err) { - record.status = 'failed'; - record.error = err.message; + const cancelled = record.status === 'cancelled' || isAbortError(err, childLink.signal); + record.status = cancelled ? 'cancelled' : 'failed'; + record.error = cancelled ? null : (err?.message || String(err)); this.emit(userId, 'run:subagent', { runId: parentRunId, handle, childRunId, - status: 'failed', - error: err.message, + status: record.status, + error: record.error, }); - throw err; + return record; + } finally { + record.settled = true; + childLink.cleanup(); + if (record.deleteWhenSettled && this.subagents.get(handle) === record) { + this.subagents.delete(handle); + } } })(); @@ -1571,21 +1711,43 @@ class AgentEngine { })); } - cleanupSubagentsForRun(parentRunId, options = {}) { - if (!parentRunId) return; + async cleanupSubagentsForRun(parentRunId, options = {}) { + if (!parentRunId) return { cancelled: 0, timedOut: 0 }; const cancelRunning = options.cancelRunning !== false; - for (const [handle, record] of this.subagents.entries()) { + const shutdowns = []; + const records = []; + for (const [handle, record] of Array.from(this.subagents.entries())) { if (record.parentRunId !== parentRunId) continue; + records.push([handle, record]); + record.deleteWhenSettled = true; if (cancelRunning && record.status === 'running') { - try { - record.engine?.abort(record.childRunId); - record.status = 'cancelled'; - } catch (err) { - console.warn(`[AgentEngine] Failed to abort subagent ${handle}:`, err?.message); - } + record.status = 'cancelled'; + this.emit(record.userId, 'run:subagent', { + runId: record.parentRunId, + handle, + childRunId: record.childRunId, + status: 'cancelled', + }); + shutdowns.push(Promise.resolve(record.engine?.shutdown?.({ + reason: 'Parent run finished before the sub-agent completed.', + timeoutMs: Math.max(100, Number(options.timeoutMs) || 10000), + }))); + } else if (record.status === 'running') { + record.deleteWhenSettled = false; } - this.subagents.delete(handle); } + const results = await Promise.allSettled(shutdowns); + for (const [handle, record] of records) { + if ((record.settled || !record.promise) && this.subagents.get(handle) === record) { + this.subagents.delete(handle); + } + } + return { + cancelled: shutdowns.length, + timedOut: results.filter((result) => ( + result.status === 'fulfilled' && result.value?.timedOut === true + )).length, + }; } async waitForSubagent(handle, options = {}) { @@ -1648,7 +1810,6 @@ class AgentEngine { }; } - record.engine?.abort(record.childRunId); record.status = 'cancelled'; this.emit(record.userId, 'run:subagent', { runId: record.parentRunId, @@ -1657,7 +1818,15 @@ class AgentEngine { status: 'cancelled', }); - return { handle, status: 'cancelled' }; + const shutdown = await record.engine?.shutdown?.({ + reason: 'Sub-agent cancelled by its parent run.', + timeoutMs: Math.max(100, Number(options.timeoutMs) || 10000), + }); + return { + handle, + status: 'cancelled', + timedOut: shutdown?.timedOut === true, + }; } interruptRun(runId, reason = 'Server shutting down while run was in progress.') { @@ -1705,21 +1874,26 @@ class AgentEngine { } } - stopRun(runId) { + stopRun(runId, reason = 'Stopped by request.') { + const stopReason = String(reason || 'Stopped by request.').slice(0, 1000); const runMeta = this.activeRuns.get(runId); - const persistedRun = runMeta || db.prepare('SELECT user_id AS userId FROM agent_runs WHERE id = ?').get(runId); + const persistedRun = runMeta || db.prepare( + 'SELECT user_id AS userId, status FROM agent_runs WHERE id = ?', + ).get(runId); + if (!persistedRun || TERMINAL_STATUSES.has(persistedRun.status)) return false; if (persistedRun?.userId != null) { - requestRunControl(runId, persistedRun.userId, 'stop', 'Stopped by request.'); + requestRunControl(runId, persistedRun.userId, 'stop', stopReason); } const delegatedChildren = db.prepare( "SELECT child_run_id FROM agent_delegations WHERE parent_run_id = ? AND status = 'running'" ).all(runId); if (runMeta) { runMeta.status = 'stopped'; + runMeta.stopReason = stopReason; runMeta.aborted = true; - runMeta.abortController?.abort('Run stopped.'); + runMeta.abortController?.abort(stopReason); runMeta.resumeRun?.(); - this.emit(runMeta.userId, 'run:stopping', { runId }); + this.emit(runMeta.userId, 'run:stopping', { runId, reason: stopReason }); for (const pid of runMeta.toolPids) { if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') { void this.runtimeManager.killCommand(runMeta.userId, pid, 'aborted'); @@ -1729,13 +1903,22 @@ class AgentEngine { } for (const child of delegatedChildren) { if (child.child_run_id && child.child_run_id !== runId) { - this.stopRun(child.child_run_id); + this.stopRun(child.child_run_id, stopReason); } } db.prepare( - "UPDATE agent_delegations SET status = 'stopped', updated_at = datetime('now'), completed_at = datetime('now') WHERE parent_run_id = ? AND status = 'running'" - ).run(runId); - closeRun(runId, 'stopped', {}, ['running', 'pausing', 'paused', 'resuming']); + `UPDATE agent_delegations + SET status = 'stopped', error = COALESCE(NULLIF(error, ''), ?), + updated_at = datetime('now'), completed_at = datetime('now') + WHERE parent_run_id = ? AND status = 'running'` + ).run(stopReason, runId); + closeRun( + runId, + 'stopped', + { error: stopReason }, + ['running', 'pausing', 'paused', 'resuming'], + ); + return true; } pauseRun(runId, { userId, reason = '' } = {}) { @@ -1777,15 +1960,17 @@ class AgentEngine { return true; } - abort(runId, { userId } = {}) { + abort(runId, { userId, reason = 'Stopped by request.' } = {}) { if (!runId) return false; + const runMeta = this.activeRuns.get(runId); + const persistedRun = runMeta + || db.prepare('SELECT user_id AS userId, status FROM agent_runs WHERE id = ?').get(runId); + if (!persistedRun || TERMINAL_STATUSES.has(persistedRun.status)) return false; if (userId != null) { - // Ownership gate: never let one user abort another user's active run. - const runMeta = this.activeRuns.get(runId); - if (runMeta && Number(runMeta.userId) !== Number(userId)) return false; + // Ownership gate applies to both active and persisted runs. + if (Number(persistedRun.userId) !== Number(userId)) return false; } - this.stopRun(runId); - return true; + return this.stopRun(runId, reason); } abortAll(userId) { diff --git a/server/services/ai/loop/callbacks.js b/server/services/ai/loop/callbacks.js index c10558ef..e0794d6a 100644 --- a/server/services/ai/loop/callbacks.js +++ b/server/services/ai/loop/callbacks.js @@ -65,6 +65,7 @@ async function publishInterimUpdate(engine, { persistConversation: true, metadata, deliveryKind: 'interim', + signal: runMeta.abortController?.signal || null, }); requireSuccessfulMessagingDelivery(deliveryResult, 'Interim messaging delivery'); } else if (triggerSource === 'voice_live') { diff --git a/server/services/ai/loop/completion_judge.js b/server/services/ai/loop/completion_judge.js index 23b2d7bc..9f89530a 100644 --- a/server/services/ai/loop/completion_judge.js +++ b/server/services/ai/loop/completion_judge.js @@ -2,6 +2,7 @@ const { normalizeCompletionConfidence } = require('../completion'); const { normalizeOutgoingMessage } = require('../messagingFallback'); +const { isDeferredWorkReply } = require('../terminal_reply'); const { summarizeAvailableTools, summarizeToolExecutions, @@ -241,6 +242,16 @@ function normalizeCompletionDecision(raw, fallbackStatus = 'continue') { }; } +function enforceTerminalReplyDecision(decision, lastReply) { + if (decision?.status === 'continue' || !isDeferredWorkReply(lastReply)) { + return decision; + } + return { + status: 'continue', + reason: 'The latest reply only announces or promises unfinished work; the run must continue or return a concrete blocker.', + }; +} + // Intentionally lightweight (200-token cap, self-contained) so the model can // answer cold without re-reading full conversation history. function buildChurnAssessmentPrompt({ @@ -285,6 +296,7 @@ module.exports = { buildChurnAssessmentPrompt, buildCompletionDecisionPrompt, buildGoalContractPrompt, + enforceTerminalReplyDecision, goalContractFromAnalysis, goalContractFromPlan, mergeGoalContracts, diff --git a/server/services/ai/loop/conversation_loop.js b/server/services/ai/loop/conversation_loop.js index 2e75039d..0020413d 100644 --- a/server/services/ai/loop/conversation_loop.js +++ b/server/services/ai/loop/conversation_loop.js @@ -55,6 +55,11 @@ const { normalizeCompletionConfidence, shouldAcceptTaskComplete } = require('../ const { enforceRateLimits } = require('../rate_limits'); const { ToolRepetitionGuard } = require('../repetitionGuard'); const { shortenRunId, summarizeForLog } = require('../logFormat'); +const { + normalizeModelSelections, + resolveModelSelection, +} = require('../model_identity'); +const { getProviderForUser } = require('../provider_selector'); const { IterationBudget } = require('./iteration_budget'); const { buildBlankAfterToolFailureGuidance, @@ -116,7 +121,7 @@ const { const { requestModelResponse: requestModelResponseImpl, requestStructuredJson: requestStructuredJsonImpl, - withModelCallTimeout, + runAbortableModelCall, } = require('./model_io'); const { publishInterimUpdate: publishInterimUpdateImpl, @@ -143,6 +148,7 @@ const { buildDeterministicMessagingErrorReply, buildModelFailureLoopPrompt, } = require('../messagingFallback'); +const { isDeferredWorkReply } = require('../terminal_reply'); const { classifyToolExecution, gatheredNewEvidence, @@ -164,6 +170,7 @@ const { normalizeRetrievalPlan, shouldEnhanceRetrieval, } = require('../../memory/retrieval_reasoning'); +const { isAbortError, throwIfAborted } = require('../../../utils/abort'); function generateTitle(task) { if (!task || typeof task !== 'string') return 'Untitled'; @@ -403,145 +410,55 @@ function buildAutonomyPolicyFromAnalysis(analysis = {}) { }; } -async function getProviderForUser(userId, task = '', isSubagent = false, modelOverride = null, providerConfig = {}) { - const { getSupportedModels, createProviderInstance } = require('../models'); - const agentId = providerConfig.agentId || null; - const aiSettings = getAiSettings(userId, agentId); - const models = await getSupportedModels(userId, agentId); - - let enabledIds = Array.isArray(aiSettings.enabled_models) ? aiSettings.enabled_models : []; - const defaultChatModel = aiSettings.default_chat_model || 'auto'; - const defaultSubagentModel = aiSettings.default_subagent_model || 'auto'; - const smarterSelection = aiSettings.smarter_model_selector !== false && aiSettings.smarter_model_selector !== 'false'; - - const knownModelIds = new Set(models.map((m) => m.id)); - const selectableModels = models.filter((m) => m.available !== false); - - enabledIds = Array.isArray(enabledIds) - ? enabledIds - .map((id) => String(id)) - .filter((id) => knownModelIds.has(id)) - : []; - - let availableModels = selectableModels.filter((m) => enabledIds.includes(m.id)); - if (availableModels.length === 0) { - enabledIds = selectableModels.map((m) => m.id); - availableModels = [...selectableModels]; - } - - const fallbackModel = availableModels.length > 0 ? availableModels[0] : selectableModels[0]; - - if (!fallbackModel) { - throw new Error('No AI providers are currently available. Open Settings and configure at least one provider.'); - } - - let selectedModelDef = fallbackModel; - const userSelectedDefault = isSubagent ? defaultSubagentModel : defaultChatModel; - - if (modelOverride && typeof modelOverride === 'string') { - const requested = models.find((m) => m.id === modelOverride.trim()); - // An explicit override (e.g. a failure fallback picked by getFailureFallbackModelId) - // may intentionally sit outside the user's enabled_models pool — that's the whole - // point when no in-pool cross-provider option exists. Only require availability. - if (requested && requested.available !== false) { - selectedModelDef = requested; - return { - provider: createProviderInstance(selectedModelDef.provider, userId, providerConfig), - model: selectedModelDef.id, - providerName: selectedModelDef.provider - }; - } - } - - if (userSelectedDefault && userSelectedDefault !== 'auto') { - selectedModelDef = models.find((m) => m.id === userSelectedDefault) || fallbackModel; - } else { - const selectionHint = providerConfig.selectionHint && typeof providerConfig.selectionHint === 'object' - ? providerConfig.selectionHint - : {}; - const preferredPurpose = String(selectionHint.purpose || '').trim().toLowerCase(); - const highAutonomy = selectionHint.autonomyLevel === 'high' || selectionHint.complexity === 'complex'; - const requiredConfidence = String(selectionHint.requiredConfidence || '').trim().toLowerCase(); - const costMode = String(selectionHint.costMode || aiSettings.cost_mode || 'balanced_auto').trim().toLowerCase(); - const requestedPurpose = ['planning', 'coding', 'general', 'fast'].includes(preferredPurpose) - ? preferredPurpose - : ''; - const priceRank = { free: 0, cheap: 1, medium: 2, expensive: 3 }; - const chooseForPurpose = (purpose) => { - const candidates = availableModels.filter((model) => model.purpose === purpose); - if (candidates.length === 0) return null; - if (['economy', 'cost_saver', 'lowest_cost'].includes(costMode)) { - return [...candidates].sort((left, right) => ( - (priceRank[left.priceTier] ?? 99) - (priceRank[right.priceTier] ?? 99) - ))[0]; - } - if (['quality', 'highest_quality'].includes(costMode) || requiredConfidence === 'high') { - return candidates.find((model) => model.priceTier !== 'free' && model.priceTier !== 'cheap') || candidates[0]; - } - return candidates[0]; - }; - - if (smarterSelection && requestedPurpose) { - selectedModelDef = chooseForPurpose(requestedPurpose) || fallbackModel; - } else if (smarterSelection && highAutonomy) { - selectedModelDef = chooseForPurpose('planning') || chooseForPurpose('general') || fallbackModel; - } else if (isSubagent) { - selectedModelDef = chooseForPurpose('fast') || fallbackModel; - } else { - selectedModelDef = chooseForPurpose('general') || fallbackModel; - } - } - - return { - provider: createProviderInstance(selectedModelDef.provider, userId, providerConfig), - model: selectedModelDef.id, - providerName: selectedModelDef.provider - }; -} - -async function getFailureFallbackModelId(userId, agentId, currentModelId, preferredFallbackId = null, failureError = null) { +async function getFailureFallbackModelId( + userId, + agentId, + currentModelId, + preferredFallbackId = null, + failureError = null, + signal = null, +) { const { getSupportedModels } = require('../models'); const aiSettings = getAiSettings(userId, agentId); - const models = await getSupportedModels(userId, agentId); + const models = await getSupportedModels(userId, agentId, { signal }); const availableModels = models.filter((model) => model.available !== false); - const knownIds = new Set(availableModels.map((model) => model.id)); - const enabledIds = Array.isArray(aiSettings.enabled_models) - ? aiSettings.enabled_models.map((id) => String(id)).filter((id) => knownIds.has(id)) + const configuredEnabledIds = Array.isArray(aiSettings.enabled_models) + ? aiSettings.enabled_models.map((id) => String(id).trim()).filter(Boolean) : []; - const pool = enabledIds.length > 0 + const enabledIds = normalizeModelSelections(availableModels, configuredEnabledIds); + const pool = configuredEnabledIds.length > 0 ? availableModels.filter((model) => enabledIds.includes(model.id)) : availableModels; - const fallbackSearchPool = pool.length > 0 ? pool : availableModels; - const currentModel = pool.find((model) => model.id === currentModelId) - || availableModels.find((model) => model.id === currentModelId) - || null; + const fallbackSearchPool = pool; + const currentModel = resolveModelSelection(pool, currentModelId) + || resolveModelSelection(availableModels, currentModelId); // When the failure is a provider-level rate limit, the preferred fallback is // likely on the same provider and will hit the same limit. Skip it and prefer // a fallback from a different provider instead. const isProviderRateLimit = /429|rate.?limit|free-models-per/i.test(String(failureError?.message || '')); - if (preferredFallbackId && preferredFallbackId !== currentModelId && !isProviderRateLimit) { - const preferred = fallbackSearchPool.find((model) => model.id === preferredFallbackId) - || availableModels.find((model) => model.id === preferredFallbackId); - if (preferred) return preferred.id; + if (preferredFallbackId && !isProviderRateLimit) { + const preferred = resolveModelSelection(fallbackSearchPool, preferredFallbackId) + || resolveModelSelection(availableModels, preferredFallbackId); + if (preferred && preferred.id !== currentModel?.id) return preferred.id; } if (currentModel?.provider) { const differentProvider = fallbackSearchPool.find((model) => - model.id !== currentModelId && model.provider !== currentModel.provider); + model.id !== currentModel.id && model.provider !== currentModel.provider); if (differentProvider) return differentProvider.id; } // If no different-provider model exists, still try the preferred fallback // even on rate limits (it's better than nothing). - if (preferredFallbackId && preferredFallbackId !== currentModelId) { - const preferred = fallbackSearchPool.find((model) => model.id === preferredFallbackId) - || availableModels.find((model) => model.id === preferredFallbackId); - if (preferred) return preferred.id; + if (preferredFallbackId) { + const preferred = resolveModelSelection(fallbackSearchPool, preferredFallbackId) + || resolveModelSelection(availableModels, preferredFallbackId); + if (preferred && preferred.id !== currentModel?.id) return preferred.id; } - const differentModel = fallbackSearchPool.find((model) => model.id !== currentModelId); + const differentModel = fallbackSearchPool.find((model) => model.id !== currentModel?.id); return differentModel?.id || null; } @@ -552,6 +469,7 @@ function estimateTokenValue(value) { } async function runConversation(engine, userId, userMessage, options = {}, _modelOverride = null) { + throwIfAborted(options.signal, 'Agent run aborted before startup.'); const triggerType = options.triggerType || 'user'; const { resolveAgentId } = require('../../agents/manager'); const agentId = resolveAgentId(userId, options.agentId || options.agent_id || null); @@ -563,6 +481,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model const triggerSource = options.triggerSource || 'web'; let provider = null; let model = null; + let modelSelectionId = null; let providerName = null; let messages = []; let iteration = 0; @@ -572,7 +491,9 @@ async function runConversation(engine, userId, userMessage, options = {}, _model let failedStepCount = 0; let toolExecutions = []; let deliverableWorkflow = null; - let runTitle; + let detachExternalAbort = null; + const runTitle = generateTitle(userMessage); + let runRecordCreated = false; const timelineService = app?.locals?.timelineService || null; const { releaseReservation } = enforceRateLimits(userId, { @@ -589,6 +510,59 @@ async function runConversation(engine, userId, userMessage, options = {}, _model // analysis when the mode is known. See the post-analysis policy rebuild below. let loopPolicy = buildLoopPolicy(aiSettings, triggerType, 'execute', options); let maxIterations = loopPolicy.maxIterations; + const initialRunMetadata = buildInitialRunMetadata(options); + const requestedModel = String( + _modelOverride + || (triggerType === 'subagent' ? aiSettings.default_subagent_model : aiSettings.default_chat_model) + || 'auto', + ).trim(); + try { + db.prepare(`INSERT INTO agent_runs( + id, user_id, agent_id, title, status, trigger_type, trigger_source, model, metadata_json + ) VALUES(?, ?, ?, ?, 'running', ?, ?, ?, ?)`).run( + runId, + userId, + agentId, + runTitle, + triggerType, + triggerSource, + requestedModel, + Object.keys(initialRunMetadata).length ? JSON.stringify(initialRunMetadata) : null, + ); + runRecordCreated = true; + } catch (error) { + if (/unique|primary key|constraint/i.test(String(error?.message || ''))) { + const conflict = new Error(`A run with id "${runId}" already exists.`); + conflict.code = 'RUN_ID_CONFLICT'; + throw conflict; + } + throw error; + } + const startupAbortController = new AbortController(); + engine.activeRuns.set(runId, { + userId, + agentId, + title: runTitle, + status: 'running', + aborted: false, + abortController: startupAbortController, + pauseAvailable: false, + toolPids: new Set(), + subagentDepth: Math.max(0, Number(options.subagentDepth) || 0), + }); + if (options.signal) { + const abortFromExternal = () => { + engine.interruptRun( + runId, + String(options.signal.reason || 'Agent run interrupted by its caller.'), + ); + }; + if (options.signal.aborted) abortFromExternal(); + else options.signal.addEventListener('abort', abortFromExternal, { once: true }); + detachExternalAbort = () => { + options.signal.removeEventListener('abort', abortFromExternal); + }; + } const providerStatusConfig = { agentId, onStatus: (status) => { @@ -605,18 +579,39 @@ async function runConversation(engine, userId, userMessage, options = {}, _model userMessage, triggerType === 'subagent', _modelOverride, - providerStatusConfig + { ...providerStatusConfig, signal: startupAbortController.signal } ); provider = selectedProvider.provider; model = selectedProvider.model; + modelSelectionId = selectedProvider.modelSelectionId; providerName = selectedProvider.providerName; - const switchToFallbackModel = async (failedModel, error, phase) => { - const fallbackModelId = await getFailureFallbackModelId(userId, agentId, failedModel, aiSettings.fallback_model_id, error); - if (!fallbackModelId || fallbackModelId === failedModel) return false; - console.log(`[Engine] ${phase} failed on ${failedModel}; attempting fallback to: ${fallbackModelId}`); + if ( + startupAbortController.signal.aborted + || engine.getRunMeta(runId)?.status !== 'running' + ) { + const startupError = new Error( + String(startupAbortController.signal.reason || 'Run stopped during model selection.'), + ); + startupError.name = 'AbortError'; + startupError.code = 'ABORT_ERR'; + throw startupError; + } + db.prepare('UPDATE agent_runs SET model = ?, updated_at = datetime(\'now\') WHERE id = ?') + .run(modelSelectionId, runId); + const switchToFallbackModel = async (failedSelectionId, error, phase) => { + const fallbackModelId = await getFailureFallbackModelId( + userId, + agentId, + failedSelectionId, + aiSettings.fallback_model_id, + error, + engine.getRunMeta(runId)?.abortController?.signal, + ); + if (!fallbackModelId || fallbackModelId === failedSelectionId) return false; + console.log(`[Engine] ${phase} failed on ${failedSelectionId}; attempting fallback to: ${fallbackModelId}`); engine.emit(userId, 'run:interim', { runId, - message: `Model service failed on ${failedModel}; retrying with ${fallbackModelId}.`, + message: `Model service failed on ${failedSelectionId}; retrying with ${fallbackModelId}.`, phase: 'model_fallback' }); const fallback = await getProviderForUser( @@ -624,46 +619,35 @@ async function runConversation(engine, userId, userMessage, options = {}, _model userMessage, triggerType === 'subagent', fallbackModelId, - providerStatusConfig + { + ...providerStatusConfig, + signal: engine.getRunMeta(runId)?.abortController?.signal, + } ); provider = fallback.provider; model = fallback.model; + modelSelectionId = fallback.modelSelectionId; providerName = fallback.providerName; + db.prepare('UPDATE agent_runs SET model = ?, updated_at = datetime(\'now\') WHERE id = ?') + .run(modelSelectionId, runId); + Object.assign(engine.getRunMeta(runId) || {}, { + model, + modelSelectionId, + providerName, + }); return true; }; const runWithModelFallback = async (phase, fn) => { try { return await fn(); } catch (err) { - const failedModel = model; - const switched = await switchToFallbackModel(failedModel, err, phase); + const failedSelectionId = modelSelectionId; + const switched = await switchToFallbackModel(failedSelectionId, err, phase); if (!switched) throw err; return await fn(); } }; - runTitle = generateTitle(userMessage); - const initialRunMetadata = buildInitialRunMetadata(options); - db.prepare(`INSERT INTO agent_runs( - id, user_id, agent_id, title, status, trigger_type, trigger_source, model, metadata_json - ) VALUES(?, ?, ?, ?, 'running', ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - status = 'running', - model = excluded.model, - updated_at = datetime('now'), - completed_at = NULL, - error = NULL, - metadata_json = COALESCE(agent_runs.metadata_json, excluded.metadata_json)`).run( - runId, - userId, - agentId, - runTitle, - triggerType, - triggerSource, - model, - Object.keys(initialRunMetadata).length ? JSON.stringify(initialRunMetadata) : null, - ); - const retryMessagingState = options.messagingRetryState || {}; const carriedFinalMessage = String(retryMessagingState.lastFinalMessage || '').trim(); const carriedExplicitMessageSent = retryMessagingState.explicitMessageSent === true; @@ -685,6 +669,9 @@ async function runConversation(engine, userId, userMessage, options = {}, _model userId, agentId, title: runTitle, + model, + modelSelectionId, + providerName, status: 'running', aborted: false, messagingSent: false, @@ -709,7 +696,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model voiceSessionId: options.voiceSessionId || null, steeringQueue: [], systemSteeringQueue: [], - abortController: new AbortController(), + abortController: startupAbortController, pauseAvailable: false, toolPids: new Set(), subagentDepth: Math.max(0, Number(options.subagentDepth) || 0), @@ -735,10 +722,21 @@ async function runConversation(engine, userId, userMessage, options = {}, _model goalContract: carriedGoalContract, }); engine.startMessagingProgressSupervisor(runId); - engine.emit(userId, 'run:start', { runId, agentId, title: runTitle, model, triggerType, triggerSource }); + engine.emit(userId, 'run:start', { + runId, + agentId, + title: runTitle, + model, + modelSelectionId, + provider: providerName, + triggerType, + triggerSource, + }); engine.recordRunEvent(userId, runId, 'run_started', { title: runTitle, model, + modelSelectionId, + provider: providerName, triggerType, triggerSource, }, { agentId }); @@ -752,7 +750,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model triggerSource, }); console.info( - `[Run ${shortenRunId(runId)}] started trigger=${triggerSource} type=${triggerType} model=${model} title=${summarizeForLog(runTitle, 120)}` + `[Run ${shortenRunId(runId)}] started trigger=${triggerSource} type=${triggerType} model=${modelSelectionId} title=${summarizeForLog(runTitle, 120)}` ); const systemPrompt = await engine.buildSystemPrompt(userId, { @@ -883,7 +881,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model if (triggerSource === 'messaging') { const runMetaForCompose = engine.getRunMeta(runId); if (runMetaForCompose) { - runMetaForCompose.composeProgressUpdate = async ({ stalled = false } = {}) => { + runMetaForCompose.composeProgressUpdate = async ({ stalled = false, signal = null } = {}) => { try { const rm = engine.getRunMeta(runId); const ledger = rm?.progressLedger || {}; @@ -900,7 +898,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model const priorUpdate = String(rm?.lastInterimMessage || '').trim(); const contextBlock = [ buildProgressUpdatePrompt(), - stalled ? 'This step has been running a while with no new progress; reassure the user you are still on it.' : '', + stalled ? 'No verified progress has occurred for the stall threshold. State that fact plainly if it matters; do not reassure, promise continued work, or imply activity beyond the evidence.' : '', '', `Original request: ${summarizeForLog(userMessage, 320)}`, currentTool ? `Doing now: using ${currentTool}` : '', @@ -911,16 +909,23 @@ async function runConversation(engine, userId, userMessage, options = {}, _model // formatting guidelines as every other message (single source of truth). const sysContent = [systemPrompt?.stable, systemPrompt?.dynamic].filter(Boolean).join('\n\n') || 'You are a helpful assistant.'; - const resp = await withModelCallTimeout( - provider.chat( + const resp = await runAbortableModelCall( + (signal) => provider.chat( [ { role: 'system', content: sysContent }, { role: 'user', content: contextBlock }, ], [], - { model, reasoningEffort: engine.getReasoningEffort(providerName, options) }, + { + model, + reasoningEffort: engine.getReasoningEffort(providerName, options), + signal, + }, ), - options, + { + ...options, + signals: [rm?.abortController?.signal, signal], + }, 'Progress update compose', ); return sanitizeModelOutput(resp.content || '', { model }); @@ -1031,6 +1036,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model null, { ...providerStatusConfig, + signal: engine.getRunMeta(runId)?.abortController?.signal, selectionHint: { purpose: requestedPurpose, complexity: analysis?.complexity, @@ -1040,15 +1046,21 @@ async function runConversation(engine, userId, userMessage, options = {}, _model }, } ); - if (selectedAfterAnalysis.model !== model) { + if (selectedAfterAnalysis.modelSelectionId !== modelSelectionId) { provider = selectedAfterAnalysis.provider; model = selectedAfterAnalysis.model; + modelSelectionId = selectedAfterAnalysis.modelSelectionId; providerName = selectedAfterAnalysis.providerName; db.prepare('UPDATE agent_runs SET model = ?, updated_at = datetime(\'now\') WHERE id = ?') - .run(model, runId); + .run(modelSelectionId, runId); + Object.assign(engine.getRunMeta(runId) || {}, { + model, + modelSelectionId, + providerName, + }); engine.emit(userId, 'run:interim', { runId, - message: `Switched to ${model} for this run after task analysis.`, + message: `Switched to ${modelSelectionId} for this run after task analysis.`, phase: 'model_selection' }); } @@ -1239,7 +1251,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model reason, stoppedBy: hookResult.stopped_by || hookResult.stoppedBy || null, }, { agentId }); - lastContent = reason; + engine.stopRun(runId, reason); break; } const systemSteering = String(hookResult?.systemSteering || hookResult?.system_steering || '').trim(); @@ -1362,8 +1374,8 @@ async function runConversation(engine, userId, userMessage, options = {}, _model }); let wrapTokens = 0; try { - const wrapResponse = await withModelCallTimeout( - provider.chat( + const wrapResponse = await runAbortableModelCall( + (signal) => provider.chat( sanitizeConversationMessages([ ...messages, { @@ -1376,9 +1388,13 @@ async function runConversation(engine, userId, userMessage, options = {}, _model }, ]), [], - { model, reasoningEffort: engine.getReasoningEffort(providerName, options) }, + { + model, + reasoningEffort: engine.getReasoningEffort(providerName, options), + signal, + }, ), - options, + { ...options, signal: engine.getRunMeta(runId)?.abortController?.signal }, 'No-progress wrap-up', ); wrapTokens = wrapResponse.usage?.totalTokens || 0; @@ -1387,7 +1403,8 @@ async function runConversation(engine, userId, userMessage, options = {}, _model console.warn(`[Run ${shortenRunId(runId)}] no_progress_wrapup failed: ${summarizeForLog(wrapErr?.message || wrapErr, 180)}`); } totalTokens += wrapTokens; - const usableWrap = normalizeOutgoingMessage(lastContent, options?.source || null); + const usableWrap = normalizeOutgoingMessage(lastContent, options?.source || null) + && !isDeferredWorkReply(lastContent); if (!usableWrap) { lastContent = buildDeterministicMessagingFallback({ failedStepCount, stepIndex, toolExecutions }); } @@ -1431,9 +1448,9 @@ async function runConversation(engine, userId, userMessage, options = {}, _model let metrics = engine.estimatePromptMetrics(messages, tools); const contextWindow = provider.getContextWindow(model); if (metrics.totalEstimatedTokens > contextWindow * loopPolicy.compactionThreshold) { - messages = await withModelCallTimeout( - compact(messages, provider, model, contextWindow), - options, + messages = await runAbortableModelCall( + (signal) => compact(messages, provider, model, contextWindow, { signal }), + { ...options, signal: engine.getRunMeta(runId)?.abortController?.signal }, `Context compaction before iteration ${iteration}`, ); messages = sanitizeConversationMessages(messages); @@ -1478,7 +1495,14 @@ async function runConversation(engine, userId, userMessage, options = {}, _model } catch (err) { console.error(`[Engine] Model call failed (${model}):`, err.message); const fallbackModelId = retryForFallback - ? await getFailureFallbackModelId(userId, agentId, model, aiSettings.fallback_model_id, err) + ? await getFailureFallbackModelId( + userId, + agentId, + modelSelectionId, + aiSettings.fallback_model_id, + err, + engine.getRunMeta(runId)?.abortController?.signal, + ) : null; if (fallbackModelId) { const failedModel = model; @@ -1488,11 +1512,22 @@ async function runConversation(engine, userId, userMessage, options = {}, _model userMessage, triggerType === 'subagent', fallbackModelId, - providerStatusConfig + { + ...providerStatusConfig, + signal: engine.getRunMeta(runId)?.abortController?.signal, + } ); provider = fallback.provider; model = fallback.model; + modelSelectionId = fallback.modelSelectionId; providerName = fallback.providerName; + db.prepare('UPDATE agent_runs SET model = ?, updated_at = datetime(\'now\') WHERE id = ?') + .run(modelSelectionId, runId); + Object.assign(engine.getRunMeta(runId) || {}, { + model, + modelSelectionId, + providerName, + }); const retryMessages = sanitizeConversationMessages([ ...messages, @@ -1512,7 +1547,14 @@ async function runConversation(engine, userId, userMessage, options = {}, _model model, messages: retryMessages, tools, - options: { ...options, userId }, + options: { + ...options, + userId, + agentId, + runId, + phase: 'model_turn_fallback', + signal: engine.getRunMeta(runId)?.abortController?.signal, + }, runId, iteration, }); @@ -1547,7 +1589,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model if (modelFailureRecoveries < loopPolicy.maxModelFailureRecoveries) { const failedModel = model; - const switched = await switchToFallbackModel(failedModel, err, 'model turn'); + const switched = await switchToFallbackModel(modelSelectionId, err, 'model turn'); if (!switched) throw err; modelFailureRecoveries += 1; failedStepCount += 1; @@ -1601,10 +1643,6 @@ async function runConversation(engine, userId, userMessage, options = {}, _model toolCallCount: response.toolCalls?.length || 0, contentPreview: String(lastContent || streamContent || '').slice(0, 240), }, { agentId }); - engine.updateRunProgress(runId, {}, { - verified: true, - }); - const assistantMessage = { role: 'assistant', content: lastContent }; if (response.toolCalls?.length) assistantMessage.tool_calls = response.toolCalls; if (response.providerContentBlocks?.length) assistantMessage.providerContentBlocks = response.providerContentBlocks; @@ -1731,7 +1769,10 @@ async function runConversation(engine, userId, userMessage, options = {}, _model const canRunParallelBatch = ( response.toolCalls.length > 1 - && response.toolCalls.every((toolCall) => engine.isReadOnlyToolCall(toolCall)) + && response.toolCalls.every((toolCall) => engine.isReadOnlyToolCall( + toolCall, + tools.find((tool) => tool?.name === toolCall?.function?.name) || null, + )) ); if (canRunParallelBatch) { const parallelToolNames = response.toolCalls @@ -1768,6 +1809,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model item.toolArgs, item.result, item.error || '', + tools.find((tool) => tool?.name === item.toolName) || null, ); execution.input = item.toolArgs; execution.artifacts = await extractArtifactsFromResult(item.toolName, item.result); @@ -1807,7 +1849,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model currentTool: null, currentStepStartedAt: null, }, { - verified: true, + verified: batchGatheredNewEvidence, }); if (analysis.mode === 'execute' || analysis.mode === 'plan_execute') { const iterMeta = engine.getRunMeta(runId); @@ -2054,11 +2096,20 @@ async function runConversation(engine, userId, userMessage, options = {}, _model }); if (lifecycleAfterTool?.action === 'stop' || lifecycleAfterTool?.action === 'interrupt') break; - const execution = classifyToolExecution(toolName, toolArgs, toolResult, toolErrorMessage); + const execution = classifyToolExecution( + toolName, + toolArgs, + toolResult, + toolErrorMessage, + tools.find((tool) => tool?.name === toolName) || null, + ); execution.input = toolArgs; const repetitionObservation = repetitionGuard?.observe(toolName, toolArgs, toolResult); - if ((execution.stateChanged && isProgressToolCall(toolName, toolArgs)) - || gatheredNewEvidence(execution, repetitionObservation)) { + const toolMadeConcreteProgress = ( + (execution.stateChanged && isProgressToolCall(toolName, toolArgs)) + || gatheredNewEvidence(execution, repetitionObservation) + ); + if (toolMadeConcreteProgress) { iterationConcreteProgress = true; } execution.artifacts = await extractArtifactsFromResult(toolName, toolResult); @@ -2206,7 +2257,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model currentTool: null, currentStepStartedAt: null, }, { - verified: true, + verified: toolMadeConcreteProgress, stepId, }); @@ -2256,20 +2307,18 @@ async function runConversation(engine, userId, userMessage, options = {}, _model if (engine.isRunStopped(runId)) { const stoppedRunMeta = engine.getRunMeta(runId); - const terminalStatus = stoppedRunMeta?.status === 'interrupted' ? 'interrupted' : 'stopped'; + const persistedTerminal = db.prepare( + 'SELECT status, error FROM agent_runs WHERE id = ?', + ).get(runId); + const terminalStatus = persistedTerminal?.status === 'interrupted' + || stoppedRunMeta?.status === 'interrupted' + ? 'interrupted' + : 'stopped'; const stopReason = stoppedRunMeta?.stopReason || null; - db.prepare( - `UPDATE agent_runs - SET status = ?, - error = COALESCE(?, error), - updated_at = datetime('now'), - completed_at = datetime('now') - WHERE id = ?` - ).run(terminalStatus, stopReason, runId); console.warn( `[Run ${shortenRunId(runId)}] ${terminalStatus} trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens}` ); - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); engine.emit(userId, terminalStatus === 'interrupted' ? 'run:interrupted' : 'run:stopped', { @@ -2320,16 +2369,20 @@ async function runConversation(engine, userId, userMessage, options = {}, _model if (budgetExhaustedWithoutCompletion) { console.warn(`[Run ${shortenRunId(runId)}] max_iteration_wrapup model=${model} iteration=${iteration}/${maxIterations}`); try { - const wrapResponse = await withModelCallTimeout( - provider.chat( + const wrapResponse = await runAbortableModelCall( + (signal) => provider.chat( sanitizeConversationMessages([ ...messages, { role: 'system', content: buildMaxIterationWrapupPrompt(options?.source || null) }, ]), [], - { model, reasoningEffort: engine.getReasoningEffort(providerName, options) }, + { + model, + reasoningEffort: engine.getReasoningEffort(providerName, options), + signal, + }, ), - options, + { ...options, signal: engine.getRunMeta(runId)?.abortController?.signal }, 'Max-iteration wrap-up', ); totalTokens += wrapResponse.usage?.totalTokens || 0; @@ -2337,7 +2390,8 @@ async function runConversation(engine, userId, userMessage, options = {}, _model // On budget exhaustion the model's last text is an untrustworthy mid-thought // fragment. Replace it with the wrap-up answer, or a clean deterministic // fallback if the wrap-up came back empty — never deliver the fragment. - const usableWrap = normalizeOutgoingMessage(wrapText, options?.source || null); + const usableWrap = normalizeOutgoingMessage(wrapText, options?.source || null) + && !isDeferredWorkReply(wrapText); lastContent = usableWrap ? wrapText : buildDeterministicMessagingFallback({ failedStepCount, stepIndex, toolExecutions }); @@ -2362,8 +2416,8 @@ async function runConversation(engine, userId, userMessage, options = {}, _model console.warn(`[Run ${shortenRunId(runId)}] blank_reply_recovery model=${model}`); let recoveredTokens = 0; try { - const recoveryResponse = await withModelCallTimeout( - provider.chat( + const recoveryResponse = await runAbortableModelCall( + (signal) => provider.chat( sanitizeConversationMessages([ ...messages, { @@ -2374,10 +2428,11 @@ async function runConversation(engine, userId, userMessage, options = {}, _model [], { model, - reasoningEffort: engine.getReasoningEffort(providerName, options) + reasoningEffort: engine.getReasoningEffort(providerName, options), + signal, } ), - options, + { ...options, signal: engine.getRunMeta(runId)?.abortController?.signal }, 'Blank messaging reply recovery', ); recoveredTokens = recoveryResponse.usage?.totalTokens || 0; @@ -2390,7 +2445,10 @@ async function runConversation(engine, userId, userMessage, options = {}, _model // own wrap-up (it summarizes what it tried / where it got blocked from the run // evidence) instead of second-guessing it into a generic blob. Only fall back to // a deterministic message when the model returned nothing usable. - const recoveredVisible = Boolean(normalizeOutgoingMessage(lastContent, options?.source || null)); + const recoveredVisible = Boolean( + normalizeOutgoingMessage(lastContent, options?.source || null) + && !isDeferredWorkReply(lastContent), + ); if (!recoveredVisible) { lastContent = buildDeterministicMessagingFallback({ failedStepCount, stepIndex, toolExecutions }); } @@ -2418,7 +2476,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model || lifecycleBeforeFinalize?.action === 'interrupt' ) { const terminal = db.prepare('SELECT status FROM agent_runs WHERE id = ?').get(runId)?.status || 'stopped'; - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); return { runId, content: '', totalTokens, iterations: iteration, status: terminal }; @@ -2536,6 +2594,19 @@ async function runConversation(engine, userId, userMessage, options = {}, _model }); } + if (!messagingSent && isDeferredWorkReply(finalResponseText)) { + engine.recordRunEvent(userId, runId, 'non_terminal_final_reply_rejected', { + iteration, + contentPreview: String(finalResponseText || '').slice(0, 240), + }, { agentId }); + finalResponseText = buildDeterministicMessagingFallback({ + failedStepCount, + stepIndex, + toolExecutions, + }); + lastContent = finalResponseText; + } + if (deliverableWorkflow && deliverablePlan) { engine.recordRunEvent(userId, runId, 'deliverable_validation_started', { type: deliverableWorkflow.selection.type, @@ -2580,8 +2651,23 @@ async function runConversation(engine, userId, userMessage, options = {}, _model db.prepare('UPDATE conversations SET total_tokens = total_tokens + ?, updated_at = datetime(\'now\') WHERE id = ?') .run(totalTokens, conversationId); if (options.skipConversationMaintenance !== true) { - refreshConversationSummary(conversationId, provider, model, historyWindow).catch((err) => { - console.error('[AI] Conversation summary refresh failed:', err.message); + engine.trackBackgroundTask( + (signal) => refreshConversationSummary( + conversationId, + provider, + model, + historyWindow, + false, + { signal }, + ), + { + key: `conversation-summary:${conversationId}`, + signal: runMeta?.abortController?.signal || null, + }, + ).catch((err) => { + if (!isAbortError(err, runMeta?.abortController?.signal) && !engine.shuttingDown) { + console.error('[AI] Conversation summary refresh failed:', err.message); + } }); } } @@ -2603,6 +2689,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model // Fallback: if this was a messaging-triggered run and no final delivery // was already sent in this run, auto-send the final assistant text. // Interim progress updates do not suppress this final delivery. + await engine.stopMessagingProgressSupervisor(runId); if (triggerSource === 'messaging' && options.source && options.chatId) { if (engine.shouldSendMessagingFinalFallback(runMeta, lastContent || '', options.source) && !lastFinalDeliveryMessage) { await engine.deliverMessagingFinalFallback({ @@ -2622,7 +2709,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model }); if (!completionWon) { const terminal = db.prepare('SELECT status FROM agent_runs WHERE id = ?').get(runId)?.status || 'stopped'; - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); return { runId, content: '', totalTokens, iterations: iteration, status: terminal }; @@ -2639,7 +2726,12 @@ async function runConversation(engine, userId, userMessage, options = {}, _model analysis, verification, historyWindow, - options: { ...options, userId, agentId }, + options: { + ...options, + userId, + agentId, + signal: engine.getRunMeta(runId)?.abortController?.signal || null, + }, }).catch((err) => { console.error('[AI] Conversation working state refresh failed:', err.message); }); @@ -2648,7 +2740,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model console.info( `[Run ${shortenRunId(runId)}] completed trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens} durationMs=${runMeta?.startedAt ? Date.now() - runMeta.startedAt : 0} finalResponse=${finalResponseText ? 'yes' : 'no'} sentMessages=${runMeta?.sentMessages?.length || 0}` ); - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); engine.emit(userId, 'run:complete', { @@ -2681,12 +2773,16 @@ async function runConversation(engine, userId, userMessage, options = {}, _model // Fire-and-forget: plugins can use this for self-improvement, memory // consolidation, analytics, or other post-run housekeeping. if (globalHooks.has('on_loop_end')) { - globalHooks.run('on_loop_end', { - userId, runId, agentId, status: 'completed', - iterations: iteration, totalTokens, - taskAnalysis: analysis, - finalContent: finalResponseText, - }).catch(() => {}); + engine.trackBackgroundTask( + (signal) => globalHooks.run('on_loop_end', { + userId, runId, agentId, status: 'completed', + iterations: iteration, totalTokens, + taskAnalysis: analysis, + finalContent: finalResponseText, + signal, + }), + { signal: runMeta?.abortController?.signal || null }, + ).catch(() => {}); } if (engine.learningManager) { try { @@ -2712,25 +2808,37 @@ async function runConversation(engine, userId, userMessage, options = {}, _model return { runId, content: lastContent, totalTokens, iterations: iteration, status: 'completed' }; } catch (err) { + if (!runRecordCreated) throw err; if (engine.isRunStopped(runId)) { - db.prepare('UPDATE agent_runs SET status = ?, updated_at = datetime(\'now\'), completed_at = datetime(\'now\') WHERE id = ?') - .run('stopped', runId); + const stoppedRunMeta = engine.getRunMeta(runId); + const persistedTerminal = db.prepare( + 'SELECT status, error FROM agent_runs WHERE id = ?', + ).get(runId); + const terminalStatus = persistedTerminal?.status === 'interrupted' + || stoppedRunMeta?.status === 'interrupted' + ? 'interrupted' + : 'stopped'; console.warn( - `[Run ${shortenRunId(runId)}] stopped trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens}` + `[Run ${shortenRunId(runId)}] ${terminalStatus} trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens}` ); - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); - engine.emit(userId, 'run:stopped', { runId, triggerSource }); - engine.recordRunEvent(userId, runId, 'run_stopped', { + engine.emit(userId, terminalStatus === 'interrupted' ? 'run:interrupted' : 'run:stopped', { + runId, + triggerSource, + reason: stoppedRunMeta?.stopReason || persistedTerminal?.error || null, + }); + engine.recordRunEvent(userId, terminalStatus === 'interrupted' ? 'run_interrupted' : 'run_stopped', { triggerSource, totalTokens, iterations: iteration, }, { agentId }); - return { runId, content: '', totalTokens, iterations: iteration, status: 'stopped' }; + return { runId, content: '', totalTokens, iterations: iteration, status: terminalStatus }; } const runMeta = engine.activeRuns.get(runId); + await engine.stopMessagingProgressSupervisor(runId); const deliverableFailureResponse = err?.deliverableResult?.summary || err?.deliverableValidation?.summary @@ -2754,16 +2862,20 @@ async function runConversation(engine, userId, userMessage, options = {}, _model content: `The run encountered a runtime error and cannot continue reliably. Use the actual run scenario below to explain the blocker naturally.\n\nScenario:\n${failureScenario || 'No additional scenario details were captured.'}\n\nDo not call tools. Write exactly one short user message. Do not ask the user to resend or restate the same task. Only ask the user for something if a specific external input, permission, or configuration change is actually required. Do not promise future work unless it will happen automatically before this reply is sent.\n\n${buildPlatformFormattingGuide(options?.source || null)}` } ]); - const modelReply = await withModelCallTimeout( - provider.chat(failedMessage, [], { + const modelReply = await runAbortableModelCall( + (signal) => provider.chat(failedMessage, [], { model, - reasoningEffort: engine.getReasoningEffort(providerName, options) + reasoningEffort: engine.getReasoningEffort(providerName, options), + signal, }), - options, + { ...options, signal: runMeta?.abortController?.signal }, 'Messaging failure reply', ); const drafted = sanitizeModelOutput(modelReply.content || '', { model }); - if (normalizeOutgoingMessage(drafted, options?.source || null)) { + if ( + normalizeOutgoingMessage(drafted, options?.source || null) + && !isDeferredWorkReply(drafted) + ) { messagingFailureContent = drafted.trim(); } } catch { @@ -2785,7 +2897,11 @@ async function runConversation(engine, userId, userMessage, options = {}, _model options.source, options.chatId, messagingFailureContent, - { runId, agentId }, + { + runId, + agentId, + signal: runMeta?.abortController?.signal || null, + }, ); requireSuccessfulMessagingDelivery(deliveryResult, 'Messaging failure delivery'); sendSucceeded = true; @@ -2810,7 +2926,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model totalTokens, }); if (!failureWon) { - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); const terminal = db.prepare('SELECT status FROM agent_runs WHERE id = ?').get(runId)?.status || 'stopped'; @@ -2820,7 +2936,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model `[Run ${shortenRunId(runId)}] failed trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens} error=${summarizeForLog(err.message, 180)}` ); - engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); + await engine.cleanupSubagentsForRun(runId, { cancelRunning: true }); engine.stopMessagingProgressSupervisor(runId); engine.activeRuns.delete(runId); engine.emit(userId, 'run:error', { runId, error: err.message }); @@ -2830,17 +2946,6 @@ async function runConversation(engine, userId, userMessage, options = {}, _model iterations: iteration, deliverableType: deliverableWorkflow?.selection?.type || null, }, { agentId }); - timelineService?.recordRunLifecycle?.({ - userId, - agentId, - runId, - title: runTitle, - eventKind: 'run_failed', - status: 'failed', - triggerSource, - error: err.message, - }); - if (messagingFailureContent) { return { runId, @@ -2853,6 +2958,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model throw err; } finally { + detachExternalAbort?.(); releaseReservation(); } } diff --git a/server/services/ai/loop/messaging_delivery.js b/server/services/ai/loop/messaging_delivery.js index 9af92cbf..00e52c96 100644 --- a/server/services/ai/loop/messaging_delivery.js +++ b/server/services/ai/loop/messaging_delivery.js @@ -6,7 +6,12 @@ const { const { normalizeOutgoingMessage, } = require('../messagingFallback'); -const { withProviderRetry, isTransientError } = require('../providerRetry'); +const { withProviderRetry } = require('../providerRetry'); +const { + abortableDelay, + getErrorCode, + getHttpStatus, +} = require('../../../utils/retry'); const { shortenRunId, summarizeForLog } = require('../logFormat'); const { TICK_MS, @@ -16,6 +21,15 @@ const { evaluateProgressLiveness, } = require('./progress_monitor'); +const SAFE_PRE_DELIVERY_NETWORK_CODES = new Set([ + 'EAI_AGAIN', + 'ENOTFOUND', + 'ECONNREFUSED', + 'ENETUNREACH', + 'EHOSTUNREACH', + 'UND_ERR_CONNECT_TIMEOUT', +]); + function isoNow() { return new Date().toISOString(); } @@ -39,9 +53,26 @@ function requireSuccessfulMessagingDelivery(result, label = 'Messaging delivery' const error = new Error(`${label} failed: ${reason}`); error.code = 'MESSAGING_DELIVERY_FAILED'; error.deliveryResult = result || null; + error.safeToRetry = result?.safeToRetry === true; + error.deliveryAmbiguous = result?.deliveryAmbiguous === true; throw error; } +function isSafeMessagingDeliveryRetry(error) { + if (!error || error.retryable === false || error.deliveryAmbiguous === true) return false; + if (error.safeToRetry === true) return true; + const result = error.deliveryResult; + if (result?.deliveryAmbiguous === true || result?.retryable === false) return false; + if (result?.success === false) return true; + + // A rate-limit response explicitly rejected the request. DNS/connect failures + // happen before a platform can accept it. Timeouts, resets, broken pipes, and + // generic 5xx responses are intentionally excluded because the message may + // already have been accepted and retrying would duplicate it. + if (getHttpStatus(error) === 429) return true; + return SAFE_PRE_DELIVERY_NETWORK_CODES.has(String(getErrorCode(error) || '')); +} + // Harness-driven progress: the originating chat must see autonomous updates during // long runs even when a weak model never calls send_interim_update itself. The // supervisor paces this call (FIRST/REPEAT thresholds + a repeat guard). The update @@ -66,13 +97,30 @@ async function sendRuntimeMessagingHeartbeat(engine, runId, options = {}) { && typeof runMeta.composeProgressUpdate === 'function') { let statusMsg = ''; try { - const composed = await runMeta.composeProgressUpdate({ stalled: options.stalled === true }); + const composed = await runMeta.composeProgressUpdate({ + stalled: options.stalled === true, + signal: runMeta.messagingProgressSupervisor?.abortController?.signal || null, + }); if (normalizeOutgoingMessage(composed, platform)) statusMsg = String(composed).trim(); } catch { /* no dynamic update available this tick */ } + const currentRunMeta = engine.getRunMeta(runId); + if ( + currentRunMeta !== runMeta + || runMeta.aborted + || runMeta.status !== 'running' + || runMeta.terminalInterim + || runMeta.finalDeliverySent + || runMeta.deliveryState?.finalContentDelivered === true + ) { + return { sent: false, skipped: true, terminal: true }; + } if (statusMsg) try { const delivery = await engine.messagingManager.sendMessage(runMeta.userId, platform, chatId, statusMsg, { runId, agentId: runMeta.agentId, + signal: runMeta.messagingProgressSupervisor?.abortController?.signal + || runMeta.abortController?.signal + || null, }); if (delivery?.success === true && delivery?.suppressed !== true) { const nowIso = isoNow(); @@ -157,11 +205,12 @@ async function deliverMessagingFinalFallback(engine, { console.info( `[Run ${shortenRunId(runId)}] messaging_fallback chunks=${chunks.length} to=${summarizeForLog(chatId, 80)}` ); + const deliveredChunks = []; for (let i = 0; i < chunks.length; i++) { if (i > 0) { const delay = Math.max(1000, Math.min(chunks[i].length * 30, 4000)); await engine.messagingManager.sendTyping(userId, platform, chatId, true, { agentId }).catch(() => {}); - await new Promise((resolve) => setTimeout(resolve, delay)); + await abortableDelay(delay, runMeta.abortController?.signal || null); } try { await withProviderRetry(async () => { @@ -170,32 +219,34 @@ async function deliverMessagingFinalFallback(engine, { platform, chatId, chunks[i], - { runId, agentId }, + { + runId, + agentId, + idempotencyKey: `${runId}:final:${i}`, + signal: runMeta.abortController?.signal || null, + }, ); return requireSuccessfulMessagingDelivery(deliveryResult, 'Final messaging delivery'); }, { ...engine.messagingDeliveryRetry, label: `MessagingDelivery ${platform}`, - isRetryable: (error) => ( - error?.retryable !== false - && ( - error?.code === 'MESSAGING_DELIVERY_FAILED' - || isTransientError(error) - ) - ), + signal: runMeta.abortController?.signal || null, + isRetryable: isSafeMessagingDeliveryRetry, }); } catch (error) { error.disableAutonomousRetry = true; + error.deliveredChunks = deliveredChunks.slice(); throw error; } + deliveredChunks.push(chunks[i]); + runMeta.lastSentMessage = chunks[i]; + if (!Array.isArray(runMeta.sentMessages)) runMeta.sentMessages = []; + runMeta.sentMessages.push(chunks[i]); } - runMeta.lastSentMessage = chunks[chunks.length - 1] || cleanedContent; - runMeta.sentMessages = Array.isArray(runMeta.sentMessages) - ? [...runMeta.sentMessages, ...chunks] - : chunks.slice(); + runMeta.lastSentMessage = deliveredChunks[deliveredChunks.length - 1] || cleanedContent; engine.markRunFinalDelivery(runId, runMeta.lastSentMessage); - return { sent: true, chunks }; + return { sent: true, chunks: deliveredChunks }; } async function tickMessagingProgressSupervisor(engine, runId) { @@ -206,54 +257,71 @@ async function tickMessagingProgressSupervisor(engine, runId) { if (runMeta.terminalInterim) { return { sent: false, skipped: true }; } + if (runMeta.progressSupervisorTickInFlight === true) { + return { sent: false, skipped: true, inFlight: true }; + } + runMeta.progressSupervisorTickInFlight = true; + let settleTick; + const tickPromise = new Promise((resolve) => { settleTick = resolve; }); + runMeta.progressSupervisorTickPromise = tickPromise; - const ledger = runMeta.progressLedger || buildInitialProgressLedger({ - startedAt: runMeta.startedAtIso || isoNow(), - }); - runMeta.progressLedger = ledger; - const liveness = evaluateProgressLiveness(runMeta); - const stalled = liveness.stalled; - - if (stalled && !ledger.stallNotifiedAt) { - engine.updateRunProgress(runId, { - stallNotifiedAt: isoNow(), - progressState: 'stalled', + try { + const ledger = runMeta.progressLedger || buildInitialProgressLedger({ + startedAt: runMeta.startedAtIso || isoNow(), }); - engine.recordRunEvent(runMeta.userId, runId, 'progress_stalled', { - currentTool: ledger.currentTool || null, - currentStep: ledger.currentStep || null, - phase: ledger.currentPhase || 'idle', - }, { agentId: runMeta.agentId }); - } + runMeta.progressLedger = ledger; + const liveness = evaluateProgressLiveness(runMeta); + const stalled = liveness.stalled; - if (!liveness.shouldNudge) { - return { sent: false, skipped: true }; - } + if (stalled && !ledger.stallNotifiedAt) { + engine.updateRunProgress(runId, { + stallNotifiedAt: isoNow(), + progressState: 'stalled', + }); + engine.recordRunEvent(runMeta.userId, runId, 'progress_stalled', { + currentTool: ledger.currentTool || null, + currentStep: ledger.currentStep || null, + phase: ledger.currentPhase || 'idle', + }, { agentId: runMeta.agentId }); + } - const lastSupervisorNudgeAtMs = timestampMs(runMeta.lastSupervisorNudgeAt, 0); - if (lastSupervisorNudgeAtMs > 0 && (Date.now() - lastSupervisorNudgeAtMs) < REPEAT_UPDATE_MS) { - return { sent: false, skipped: true }; - } + if (!liveness.shouldNudge) { + return { sent: false, skipped: true }; + } - if (ledger.currentPhase === 'tool' || ledger.currentPhase === 'model') { - return sendRuntimeMessagingHeartbeat(engine, runId, { stalled }); - } + const lastSupervisorNudgeAtMs = timestampMs(runMeta.lastSupervisorNudgeAt, 0); + if (lastSupervisorNudgeAtMs > 0 && (Date.now() - lastSupervisorNudgeAtMs) < REPEAT_UPDATE_MS) { + return { sent: false, skipped: true }; + } - if (ledger.currentPhase !== 'idle') { - return { sent: false, skipped: true }; - } + if (ledger.currentPhase === 'tool' || ledger.currentPhase === 'model') { + return await sendRuntimeMessagingHeartbeat(engine, runId, { stalled }); + } - const queued = engine.enqueueSystemSteering(runId, buildProgressNudge({ stalled }), { - reason: stalled ? 'stalled_progress_check' : 'progress_check', - }); - if (!queued) { - return { sent: false, skipped: true }; + if (ledger.currentPhase !== 'idle') { + return { sent: false, skipped: true }; + } + + const queued = engine.enqueueSystemSteering(runId, buildProgressNudge({ stalled }), { + reason: stalled ? 'stalled_progress_check' : 'progress_check', + }); + if (!queued) { + return { sent: false, skipped: true }; + } + runMeta.lastSupervisorNudgeAt = isoNow(); + engine.updateRunProgress(runId, { + lastUserVisibleUpdateAt: ledger.lastUserVisibleUpdateAt || null, + }); + return { sent: false, queued: true }; + } finally { + settleTick(); + if (engine.getRunMeta(runId) === runMeta) { + runMeta.progressSupervisorTickInFlight = false; + if (runMeta.progressSupervisorTickPromise === tickPromise) { + runMeta.progressSupervisorTickPromise = null; + } + } } - runMeta.lastSupervisorNudgeAt = isoNow(); - engine.updateRunProgress(runId, { - lastUserVisibleUpdateAt: ledger.lastUserVisibleUpdateAt || null, - }); - return { sent: false, queued: true }; } function startMessagingProgressSupervisor(engine, runId) { @@ -264,25 +332,29 @@ function startMessagingProgressSupervisor(engine, runId) { if (runMeta.messagingProgressSupervisor?.timer) { return true; } + const abortController = new AbortController(); const timer = setInterval(() => { tickMessagingProgressSupervisor(engine, runId).catch((error) => { console.warn('[Engine] Messaging progress supervisor failed:', error?.message || error); }); }, TICK_MS); timer.unref?.(); - runMeta.messagingProgressSupervisor = { timer }; + runMeta.messagingProgressSupervisor = { timer, abortController }; return true; } function stopMessagingProgressSupervisor(engine, runId) { const runMeta = engine.getRunMeta(runId); + const inFlight = runMeta?.progressSupervisorTickPromise || null; const timer = runMeta?.messagingProgressSupervisor?.timer || null; if (timer) { clearInterval(timer); } + runMeta?.messagingProgressSupervisor?.abortController?.abort('Progress supervisor stopped.'); if (runMeta?.messagingProgressSupervisor) { runMeta.messagingProgressSupervisor = null; } + return inFlight || Promise.resolve(); } module.exports = { diff --git a/server/services/ai/loop/model_call_guard.js b/server/services/ai/loop/model_call_guard.js new file mode 100644 index 00000000..3e6a491b --- /dev/null +++ b/server/services/ai/loop/model_call_guard.js @@ -0,0 +1,91 @@ +'use strict'; + +const MODEL_CALL_TIMEOUT_MS = 5 * 60 * 1000; + +function formatElapsedDuration(durationMs) { + const totalSeconds = Math.max(1, Math.floor(Number(durationMs || 0) / 1000)); + if (totalSeconds < 60) return `${totalSeconds}s`; + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + if (seconds === 0) return `${minutes}m`; + return `${minutes}m ${seconds}s`; +} + +function resolveModelCallTimeoutMs(options = {}) { + const requested = Number(options?.modelCallTimeoutMs); + if (Number.isFinite(requested) && requested > 0) { + return Math.max(10, requested); + } + return MODEL_CALL_TIMEOUT_MS; +} + +function abortError(reason = null) { + if (reason instanceof Error) return reason; + const error = new Error(String(reason || 'Model call aborted.')); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +async function withModelCallTimeout(promise, options = {}, label = 'Model call') { + const timeoutMs = resolveModelCallTimeoutMs(options); + const signal = options?.modelAbortController?.signal || options?.signal || null; + let timer = null; + let abortListener = null; + const timeout = new Promise((_, reject) => { + timer = setTimeout(() => { + const error = new Error(`${label} timed out after ${formatElapsedDuration(timeoutMs)}.`); + error.code = 'MODEL_CALL_TIMEOUT'; + options?.modelAbortController?.abort(error); + reject(error); + }, timeoutMs); + }); + const aborted = new Promise((_, reject) => { + if (!signal) return; + abortListener = () => reject(abortError(signal.reason)); + if (signal.aborted) abortListener(); + else signal.addEventListener('abort', abortListener, { once: true }); + }); + + try { + return await Promise.race([Promise.resolve(promise), timeout, aborted]); + } finally { + if (timer) clearTimeout(timer); + if (abortListener) signal?.removeEventListener('abort', abortListener); + } +} + +async function runAbortableModelCall(factory, options = {}, label = 'Model call') { + const modelAbortController = new AbortController(); + const parentSignals = [...new Set([ + options?.signal, + ...(Array.isArray(options?.signals) ? options.signals : []), + ].filter((signal) => signal && typeof signal.addEventListener === 'function'))]; + const parentListeners = new Map(); + for (const parentSignal of parentSignals) { + const abortFromParent = () => modelAbortController.abort(parentSignal.reason); + parentListeners.set(parentSignal, abortFromParent); + if (parentSignal.aborted) abortFromParent(); + else parentSignal.addEventListener('abort', abortFromParent, { once: true }); + } + + try { + const promise = Promise.resolve().then(() => factory(modelAbortController.signal)); + return await withModelCallTimeout( + promise, + { ...options, modelAbortController }, + label, + ); + } finally { + for (const [parentSignal, abortFromParent] of parentListeners) { + parentSignal.removeEventListener('abort', abortFromParent); + } + } +} + +module.exports = { + abortError, + resolveModelCallTimeoutMs, + runAbortableModelCall, + withModelCallTimeout, +}; diff --git a/server/services/ai/loop/model_io.js b/server/services/ai/loop/model_io.js index 379822f5..2164ffa3 100644 --- a/server/services/ai/loop/model_io.js +++ b/server/services/ai/loop/model_io.js @@ -5,48 +5,16 @@ const { sanitizeModelOutput } = require('../outputSanitizer'); const { parseJsonObject } = require('../taskAnalysis'); const { withProviderRetry, isTransientError } = require('../providerRetry'); const { normalizeUsage, recordModelUsage } = require('../usage'); - -const MODEL_CALL_TIMEOUT_MS = 5 * 60 * 1000; +const { + resolveModelCallTimeoutMs, + runAbortableModelCall, + withModelCallTimeout, +} = require('./model_call_guard'); function isoNow() { return new Date().toISOString(); } -function formatElapsedDuration(durationMs) { - const totalSeconds = Math.max(1, Math.floor(Number(durationMs || 0) / 1000)); - if (totalSeconds < 60) return `${totalSeconds}s`; - const minutes = Math.floor(totalSeconds / 60); - const seconds = totalSeconds % 60; - if (seconds === 0) return `${minutes}m`; - return `${minutes}m ${seconds}s`; -} - -function resolveModelCallTimeoutMs(options = {}) { - const requested = Number(options?.modelCallTimeoutMs); - if (Number.isFinite(requested) && requested > 0) { - return Math.max(10, requested); - } - return MODEL_CALL_TIMEOUT_MS; -} - -async function withModelCallTimeout(promise, options = {}, label = 'Model call') { - const timeoutMs = resolveModelCallTimeoutMs(options); - let timer = null; - const timeout = new Promise((_, reject) => { - timer = setTimeout(() => { - options?.modelAbortController?.abort(`${label} timed out.`); - const error = new Error(`${label} timed out after ${formatElapsedDuration(timeoutMs)}.`); - error.code = 'MODEL_CALL_TIMEOUT'; - reject(error); - }, timeoutMs); - }); - try { - return await Promise.race([Promise.resolve(promise), timeout]); - } finally { - if (timer) clearTimeout(timer); - } -} - async function requestStructuredJson(engine, { provider, providerName, @@ -76,7 +44,6 @@ async function requestStructuredJson(engine, { }); } - let completed = false; try { const response = await withProviderRetry( () => withModelCallTimeout( @@ -99,9 +66,9 @@ async function requestStructuredJson(engine, { { label: `Engine ${model} (structured)`, isRetryable: (err) => !modelAbortController.signal.aborted && isTransientError(err), + signal: modelAbortController.signal, } ); - completed = true; if (telemetry?.runId && telemetry?.userId) { recordModelUsage({ runId: telemetry.runId, @@ -132,8 +99,6 @@ async function requestStructuredJson(engine, { currentStep: null, currentTool: null, currentStepStartedAt: null, - }, { - verified: completed, }); } } @@ -235,6 +200,7 @@ async function requestModelResponse(engine, { phase: 'recovering', }); }, + signal: modelAbortController.signal, })); } finally { parentSignal?.removeEventListener('abort', abortFromParent); @@ -279,5 +245,6 @@ module.exports = { requestModelResponse, requestStructuredJson, resolveModelCallTimeoutMs, + runAbortableModelCall, withModelCallTimeout, }; diff --git a/server/services/ai/loop/tool_dispatch.js b/server/services/ai/loop/tool_dispatch.js index 47234cc0..07b96104 100644 --- a/server/services/ai/loop/tool_dispatch.js +++ b/server/services/ai/loop/tool_dispatch.js @@ -4,7 +4,10 @@ const { v4: uuidv4 } = require('uuid'); const db = require('../../../db/database'); const { globalHooks } = require('../hooks'); const { summarizeForLog } = require('../logFormat'); -const { inferToolFailureMessage } = require('../toolEvidence'); +const { + inferToolFailureMessage, + resolveDeclaredToolAccess, +} = require('../toolEvidence'); function getAvailableTools(_engine, app, options = {}) { const { getAvailableTools: loadAvailableTools } = require('../tools'); @@ -16,8 +19,21 @@ async function executeTool(engine, toolName, args, context) { return runTool(toolName, args, context, engine); } -function isReadOnlyToolCall(toolCall) { +function isReadOnlyToolCall(toolCall, toolDefinition = null) { const name = String(toolCall?.function?.name || ''); + let toolArgs = {}; + try { + toolArgs = typeof toolCall?.function?.arguments === 'string' + ? JSON.parse(toolCall.function.arguments || '{}') + : (toolCall?.function?.arguments || {}); + } catch { + return false; + } + + const declaredAccess = resolveDeclaredToolAccess(toolDefinition, toolArgs); + if (declaredAccess === 'read') return true; + if (declaredAccess === 'write') return false; + const readOnly = new Set([ 'read_file', 'read_files', @@ -35,12 +51,7 @@ function isReadOnlyToolCall(toolCall) { 'read_health_data', ]); if (name === 'http_request') { - try { - const args = JSON.parse(toolCall.function.arguments || '{}'); - return String(args.method || 'GET').toUpperCase() === 'GET'; - } catch { - return false; - } + return String(toolArgs.method || 'GET').toUpperCase() === 'GET'; } return readOnly.has(name); } diff --git a/server/services/ai/loopPolicy.js b/server/services/ai/loopPolicy.js index e648bece..65f9a9f9 100644 --- a/server/services/ai/loopPolicy.js +++ b/server/services/ai/loopPolicy.js @@ -1,9 +1,9 @@ -// Limits resolve in priority order: per-run options → agent AI settings → hardcoded defaults. +// Limits resolve in priority order: per-run options → agent AI settings → conservative defaults. // They are safety nets only; task_complete / progress guards are the real exit signals. // The iteration ceiling is a pure runaway safety net, NOT the primary stop signal. // A run stops when it makes no real progress (consecutiveReadOnlyIterations cap, -// which resets the moment the agent does anything state-changing), or when the +// which resets on a state change or genuinely new evidence), or when the // repetition / tool-failure / model-recovery guards fire, or when the model signals // task_complete. These ceilings are set high so they only ever catch a genuine // runaway and never guillotine a long, legitimately-progressing complex task. @@ -13,8 +13,8 @@ const DEFAULT_PLAN_EXECUTE_MAX_ITERATIONS = 250; // Less aggressive than 0.60 so the model retains file contents it already read for // longer, instead of losing them to compaction and re-reading the same files. const DEFAULT_COMPACTION_THRESHOLD = 0.80; -// The real "stop when stuck" guard. Counts consecutive turns of pure reading/ -// searching/inspecting with zero state change; resets to 0 on any concrete progress. +// The real "stop when stuck" guard. Counts consecutive turns with no state +// change and no new evidence; resets to 0 on any concrete progress. const DEFAULT_MAX_CONSECUTIVE_READ_ONLY_ITERATIONS = 8; const DEFAULT_MAX_CONSECUTIVE_TOOL_FAILURES = 5; const DEFAULT_MAX_MODEL_FAILURE_RECOVERIES = 3; @@ -25,8 +25,9 @@ const MAX_ALLOWED_TOOL_FAILURES = 50; const MAX_ALLOWED_MODEL_RECOVERIES = 10; const MAX_ALLOWED_BUDGET_CHARS = 500_000; -function finitePositive(n, fallback) { - return Number.isFinite(n) && n > 0 ? n : fallback; +function optionalNumber(value) { + if (value == null || value === '') return Number.NaN; + return Number(value); } function clampFinite(n, lo, hi, fallback) { @@ -70,23 +71,27 @@ function buildLoopPolicy(aiSettings = {}, triggerType = 'chat', analysisMode = ' // ── Tool result size budget ─────────────────────────────────────────────── // Must be a finite positive integer; bad values fall back to 2400. - const defaultBudget = clampFinite( - Math.floor(Number(aiSettings.tool_replay_budget_chars) || 0), - 500, - MAX_ALLOWED_BUDGET_CHARS, - 2400, - ); + const requestedDefaultBudget = optionalNumber(aiSettings.tool_replay_budget_chars); + const defaultBudget = Number.isFinite(requestedDefaultBudget) && requestedDefaultBudget > 0 + ? clampFinite(Math.floor(requestedDefaultBudget), 500, MAX_ALLOWED_BUDGET_CHARS, 2400) + : 2400; // ── Scalar policy fields ───────────────────────────────────────────────── const maxConsecutiveToolFailures = clampFinite( - Math.floor(Number(aiSettings.max_consecutive_tool_failures)), + Math.floor(optionalNumber( + options.maxConsecutiveToolFailures + ?? aiSettings.max_consecutive_tool_failures, + )), 1, MAX_ALLOWED_TOOL_FAILURES, DEFAULT_MAX_CONSECUTIVE_TOOL_FAILURES, ); const maxModelFailureRecoveries = clampFinite( - Math.floor(Number(aiSettings.max_model_failure_recoveries)), + Math.floor(optionalNumber( + options.maxModelFailureRecoveries + ?? aiSettings.max_model_failure_recoveries, + )), 0, MAX_ALLOWED_MODEL_RECOVERIES, DEFAULT_MAX_MODEL_FAILURE_RECOVERIES, @@ -94,7 +99,7 @@ function buildLoopPolicy(aiSettings = {}, triggerType = 'chat', analysisMode = ' const rawReadOnlyIterations = options.maxConsecutiveReadOnlyIterations != null ? Number(options.maxConsecutiveReadOnlyIterations) - : Number(aiSettings.max_consecutive_read_only_iterations); + : optionalNumber(aiSettings.max_consecutive_read_only_iterations); const maxConsecutiveReadOnlyIterations = clampFinite( Math.floor(rawReadOnlyIterations), 3, @@ -104,7 +109,7 @@ function buildLoopPolicy(aiSettings = {}, triggerType = 'chat', analysisMode = ' // compactionThreshold must be in (0, 1]; clamp to [0.1, 1]. const compactionThreshold = clampFinite( - Number(aiSettings.compaction_threshold), + optionalNumber(options.compactionThreshold ?? aiSettings.compaction_threshold), 0.1, 1, DEFAULT_COMPACTION_THRESHOLD, @@ -122,9 +127,9 @@ function buildLoopPolicy(aiSettings = {}, triggerType = 'chat', analysisMode = ' // Per-category tool result size budgets (chars) toolResultBudget: { default: defaultBudget, - file: clampFinite(Math.floor(Number(aiSettings.tool_replay_budget_file_chars)), 500, MAX_ALLOWED_BUDGET_CHARS, Math.max(defaultBudget, 6000)), - browser: clampFinite(Math.floor(Number(aiSettings.tool_replay_budget_browser_chars)), 500, MAX_ALLOWED_BUDGET_CHARS, Math.max(defaultBudget, 4000)), - command: clampFinite(Math.floor(Number(aiSettings.tool_replay_budget_command_chars)), 500, MAX_ALLOWED_BUDGET_CHARS, Math.max(defaultBudget, 4000)), + file: clampFinite(Math.floor(optionalNumber(aiSettings.tool_replay_budget_file_chars)), 500, MAX_ALLOWED_BUDGET_CHARS, Math.max(defaultBudget, 6000)), + browser: clampFinite(Math.floor(optionalNumber(aiSettings.tool_replay_budget_browser_chars)), 500, MAX_ALLOWED_BUDGET_CHARS, Math.max(defaultBudget, 4000)), + command: clampFinite(Math.floor(optionalNumber(aiSettings.tool_replay_budget_command_chars)), 500, MAX_ALLOWED_BUDGET_CHARS, Math.max(defaultBudget, 4000)), }, // Hard ceiling is always 2× soft, capped at a reasonable absolute max diff --git a/server/services/ai/model_discovery.js b/server/services/ai/model_discovery.js new file mode 100644 index 00000000..dfcc4742 --- /dev/null +++ b/server/services/ai/model_discovery.js @@ -0,0 +1,227 @@ +'use strict'; + +const crypto = require('crypto'); + +const REFRESH_INTERVAL_MS = 5 * 60 * 1000; +const PERMANENT_ERROR_BACKOFF_MS = 30 * 60 * 1000; +const DISCOVERY_TIMEOUT_MS = 10_000; +const OLLAMA_REFRESH_INTERVAL_MS = 30_000; + +const providerModelCache = new Map(); +const providerRefreshes = new Map(); +const ollamaModelCache = new Map(); +const ollamaRefreshes = new Map(); +const openrouterPricingCache = new Map(); + +function inferModelPurpose(id) { + const value = id.toLowerCase(); + if (/flash|nano|lite|tiny|haiku|scout|mini(?!max)|small/.test(value)) return 'fast'; + if (/r1|qwq|o[0-9]|reasoning|thinking/.test(value)) return 'planning'; + if (/code|coder|starcoder|devstral|codex|codegemma/.test(value)) return 'coding'; + return 'general'; +} + +const PROVIDER_LABELS = Object.freeze({ + openai: (model) => `${model.id} (OpenAI)`, + anthropic: (model) => `${model.name || model.id} (Anthropic)`, + google: (model) => `${model.name || model.id} (Google)`, + nvidia: (model) => `${model.id} (NVIDIA NIM)`, + grok: (model) => `${model.id} (xAI)`, + 'grok-oauth': (model) => `${model.id} (xAI OAuth)`, + openrouter: (model) => `${model.name || model.id} (OpenRouter)`, +}); + +function cacheKeyForProvider(providerId, apiKey, baseUrl) { + return crypto + .createHash('sha256') + .update(String(providerId || '')) + .update('\0') + .update(String(apiKey || '')) + .update('\0') + .update(String(baseUrl || '')) + .digest('hex'); +} + +function abortError(signal, fallback = 'Model discovery aborted.') { + if (signal?.reason instanceof Error) return signal.reason; + const error = new Error(String(signal?.reason || fallback)); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +function waitForSharedResult(promise, signal) { + if (!signal) return promise; + if (signal.aborted) return Promise.reject(abortError(signal)); + + return new Promise((resolve, reject) => { + const onAbort = () => reject(abortError(signal)); + signal.addEventListener('abort', onAbort, { once: true }); + promise.then(resolve, reject).finally(() => { + signal.removeEventListener('abort', onAbort); + }); + }); +} + +async function runDiscovery(factory, timeoutMs = DISCOVERY_TIMEOUT_MS) { + const controller = new AbortController(); + let timer = null; + const timeout = new Promise((_, reject) => { + timer = setTimeout(() => { + const error = new Error(`Model discovery timed out after ${timeoutMs}ms.`); + error.code = 'MODEL_DISCOVERY_TIMEOUT'; + controller.abort(error); + reject(error); + }, timeoutMs); + }); + + try { + return await Promise.race([ + Promise.resolve().then(() => factory(controller.signal)), + timeout, + ]); + } finally { + clearTimeout(timer); + } +} + +function normalizeRawModels(rawModels, providerId, fallbackIds = []) { + const source = Array.isArray(rawModels) && rawModels.length > 0 + ? rawModels + : fallbackIds; + const labelModel = PROVIDER_LABELS[providerId] || ((model) => model.name || model.id); + const normalized = []; + const seen = new Set(); + + for (const raw of source) { + const model = typeof raw === 'string' ? { id: raw, name: raw } : raw; + const id = String(model?.id || '').trim(); + if (!id || seen.has(id)) continue; + seen.add(id); + normalized.push({ + id, + label: labelModel({ ...model, id }), + provider: providerId, + purpose: inferModelPurpose(id), + }); + } + return normalized; +} + +function updateOpenRouterPricing(rawModels) { + if (!Array.isArray(rawModels)) return; + for (const model of rawModels) { + if (!model || typeof model !== 'object' || model.pricing?.prompt == null) continue; + const inputPerM = Number.parseFloat(model.pricing.prompt) * 1_000_000; + if (!Number.isFinite(inputPerM) || inputPerM < 0) continue; + openrouterPricingCache.set(model.id, inputPerM); + if (!model.id.includes('/')) continue; + const bareId = model.id.slice(model.id.indexOf('/') + 1); + if (!openrouterPricingCache.has(bareId)) { + openrouterPricingCache.set(bareId, inputPerM); + } + } +} + +function isPermanentDiscoveryError(error) { + return /401|403|unauthorized|forbidden|credits|spending/i.test(String(error?.message || '')); +} + +async function loadProviderModels({ providerId, factory, apiKey, baseUrl, existing }) { + let provider = null; + try { + const config = {}; + if (factory.apiKey) config.apiKey = apiKey; + if (factory.baseUrl) config.baseUrl = baseUrl; + provider = new factory.Provider(config); + const rawModels = await runDiscovery((signal) => provider.listModels(signal)); + if (providerId === 'openrouter') updateOpenRouterPricing(rawModels); + const models = normalizeRawModels(rawModels, providerId, provider.models); + const entry = { models, expiresAt: Date.now() + REFRESH_INTERVAL_MS }; + return entry; + } catch (error) { + console.warn(`[Models] Failed to refresh ${providerId} models:`, error.message); + const models = existing?.models?.length + ? existing.models + : normalizeRawModels([], providerId, provider?.models || []); + const retryAfterMs = isPermanentDiscoveryError(error) + ? PERMANENT_ERROR_BACKOFF_MS + : REFRESH_INTERVAL_MS; + return { models, expiresAt: Date.now() + retryAfterMs }; + } +} + +async function refreshProviderModelList({ providerId, factory, apiKey, baseUrl, signal }) { + const cacheKey = cacheKeyForProvider(providerId, apiKey, baseUrl); + const existing = providerModelCache.get(cacheKey); + if (existing && existing.expiresAt > Date.now()) return existing.models; + + let refresh = providerRefreshes.get(cacheKey); + if (!refresh) { + refresh = loadProviderModels({ providerId, factory, apiKey, baseUrl, existing }) + .then((entry) => { + providerModelCache.set(cacheKey, entry); + return entry.models; + }) + .finally(() => providerRefreshes.delete(cacheKey)); + providerRefreshes.set(cacheKey, refresh); + } + return waitForSharedResult(refresh, signal); +} + +async function loadOllamaModels({ baseUrl, Provider, existing }) { + try { + const provider = new Provider({ baseUrl }); + const rawModels = await runDiscovery((signal) => provider.listModels(signal)); + const models = normalizeRawModels(rawModels, 'ollama').map((model) => ({ + ...model, + label: `${model.id} (Ollama / Local)`, + purpose: 'general', + })); + return { models, expiresAt: Date.now() + OLLAMA_REFRESH_INTERVAL_MS }; + } catch (error) { + console.warn('[Models] Failed to refresh Ollama models:', error.message); + return { + models: existing?.models || [], + expiresAt: Date.now() + OLLAMA_REFRESH_INTERVAL_MS, + }; + } +} + +async function refreshOllamaModels({ baseUrl, Provider, signal }) { + const cacheKey = String(baseUrl || ''); + const existing = ollamaModelCache.get(cacheKey); + if (existing && existing.expiresAt > Date.now()) return existing.models; + + let refresh = ollamaRefreshes.get(cacheKey); + if (!refresh) { + refresh = loadOllamaModels({ baseUrl, Provider, existing }) + .then((entry) => { + ollamaModelCache.set(cacheKey, entry); + return entry.models; + }) + .finally(() => ollamaRefreshes.delete(cacheKey)); + ollamaRefreshes.set(cacheKey, refresh); + } + return waitForSharedResult(refresh, signal); +} + +function getInputCostPerM(modelId) { + return openrouterPricingCache.get(modelId); +} + +function classifyPriceTier(modelId) { + const costPerM = getInputCostPerM(modelId); + if (costPerM === undefined) return null; + if (costPerM === 0) return 'free'; + if (costPerM < 0.5) return 'cheap'; + if (costPerM < 5) return 'medium'; + return 'expensive'; +} + +module.exports = { + classifyPriceTier, + getInputCostPerM, + refreshOllamaModels, + refreshProviderModelList, +}; diff --git a/server/services/ai/model_identity.js b/server/services/ai/model_identity.js new file mode 100644 index 00000000..08f0daab --- /dev/null +++ b/server/services/ai/model_identity.js @@ -0,0 +1,71 @@ +'use strict'; + +const MODEL_SELECTION_SEPARATOR = '::'; + +function createModelSelectionId(provider, modelId) { + return `${String(provider).trim().toLowerCase()}${MODEL_SELECTION_SEPARATOR}${String(modelId).trim()}`; +} + +function getRawModelId(model) { + return String(model?.modelId || model?.id || '').trim(); +} + +function toSelectableModel(model) { + const provider = String(model?.provider || '').trim().toLowerCase(); + const modelId = getRawModelId(model); + return { + ...model, + id: createModelSelectionId(provider, modelId), + modelId, + provider, + }; +} + +function resolveModelSelection(models, value, options = {}) { + const requested = String(value || '').trim(); + if (!requested) return null; + + const exact = models.find((model) => model.id === requested); + if (exact) return exact; + + const legacyMatches = models.filter((model) => getRawModelId(model) === requested); + if (legacyMatches.length <= 1) return legacyMatches[0] || null; + + const preferredProvider = String(options.preferredProvider || '').trim().toLowerCase(); + if (preferredProvider) { + const preferred = legacyMatches.find((model) => model.provider === preferredProvider); + if (preferred) return preferred; + } + + // Preserve the catalog's stable ordering for legacy, unscoped settings. New + // selections always use the provider-scoped id and are therefore unambiguous. + return legacyMatches[0]; +} + +function normalizeModelSelections(models, values) { + if (!Array.isArray(values)) return []; + const normalized = []; + const seen = new Set(); + for (const value of values) { + const model = resolveModelSelection(models, value); + if (!model || seen.has(model.id)) continue; + seen.add(model.id); + normalized.push(model.id); + } + return normalized; +} + +function modelMatchesConfiguredId(model, configuredIds) { + if (!configuredIds) return false; + return configuredIds.has(model.id) || configuredIds.has(getRawModelId(model)); +} + +module.exports = { + MODEL_SELECTION_SEPARATOR, + createModelSelectionId, + getRawModelId, + modelMatchesConfiguredId, + normalizeModelSelections, + resolveModelSelection, + toSelectableModel, +}; diff --git a/server/services/ai/models.js b/server/services/ai/models.js index 0909ee1d..c39d9bea 100644 --- a/server/services/ai/models.js +++ b/server/services/ai/models.js @@ -1,3 +1,5 @@ +'use strict'; + const { AnthropicProvider } = require('./providers/anthropic'); const { GoogleProvider } = require('./providers/google'); const { GrokProvider } = require('./providers/grok'); @@ -14,6 +16,19 @@ const { getProviderConfigs, getProviderSecrets, } = require('./settings'); +const { + createModelSelectionId, + modelMatchesConfiguredId, + toSelectableModel, +} = require('./model_identity'); +const { + classifyPriceTier, + getInputCostPerM, + refreshOllamaModels, + refreshProviderModelList, +} = require('./model_discovery'); +const { fetchResponseText } = require('../network/http'); +const { createAbortError, isAbortError, throwIfAborted } = require('../../utils/abort'); const STATIC_MODELS = [ // — xAI OAuth — fallback entries shown when grok-oauth token is invalid/exhausted. @@ -130,127 +145,27 @@ const PROVIDER_FACTORIES = Object.freeze({ openrouter: { Provider: OpenRouterProvider, apiKey: true, baseUrl: true }, }); -const dynamicModelsByBaseUrl = new Map(); -const REFRESH_INTERVAL = 30000; // 30 seconds - -// Unified dynamic model cache for all API-backed providers. -// Keyed by `${providerId}:${apiKey.slice(0,8)}` to handle per-user keys. -const providerModelCache = new Map(); -const DYNAMIC_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes - -// Populated from OpenRouter's /models response; used to price-classify models -// from all providers. Keyed by both the full OpenRouter ID ("openai/gpt-5-mini") -// and the bare model ID ("gpt-5-mini") for cross-provider lookup. -const openrouterPricingCache = new Map(); - // Providers whose full model list is fetched from their API at runtime. // grok-oauth inherits listModels() from GrokProvider and uses the same xAI endpoint. const DYNAMIC_PROVIDERS = ['openai', 'anthropic', 'google', 'nvidia', 'grok', 'grok-oauth', 'openrouter']; -function inferModelPurpose(id) { - const s = id.toLowerCase(); - if (/flash|nano|lite|tiny|haiku|scout|mini(?!max)|small/.test(s)) return 'fast'; - if (/r1|qwq|o[0-9]|reasoning|thinking/.test(s)) return 'planning'; - if (/code|coder|starcoder|devstral|codex|codegemma/.test(s)) return 'coding'; - return 'general'; -} - -// Pricing tiers: free=$0 cheap<$0.50/1M medium=$0.50–$5/1M expensive>$5/1M -// Uses live prices from openrouterPricingCache; returns null when unknown. -function classifyPriceTier(modelId) { - const costPerM = openrouterPricingCache.get(modelId); - if (costPerM === undefined) return null; - if (costPerM === 0) return 'free'; - if (costPerM < 0.5) return 'cheap'; - if (costPerM < 5) return 'medium'; - return 'expensive'; -} - -// Per-provider functions that turn a raw model object from listModels() into a display label. -const PROVIDER_LABEL_FN = { - openai: (m) => `${m.id} (OpenAI)`, - anthropic: (m) => `${m.name || m.id} (Anthropic)`, - google: (m) => `${m.name || m.id} (Google)`, - nvidia: (m) => `${m.id} (NVIDIA NIM)`, - grok: (m) => `${m.id} (xAI)`, - openrouter:(m) => `${m.name || m.id} (OpenRouter)`, -}; - -async function refreshProviderModelList(providerId, apiKey, baseUrl) { - const cacheKey = `${providerId}:${(apiKey || '').slice(0, 8)}`; - const existing = providerModelCache.get(cacheKey); - const now = Date.now(); - - if (existing && now - existing.lastRefresh <= DYNAMIC_REFRESH_INTERVAL) { - return existing.models; - } - - try { - const factory = PROVIDER_FACTORIES[providerId]; - const config = {}; - if (factory.apiKey) config.apiKey = apiKey; - if (factory.baseUrl) config.baseUrl = baseUrl; - const provider = new factory.Provider(config); - - const raw = await provider.listModels(); - - // OpenRouter returns live pricing — populate the shared cache so all - // other providers can resolve their price tier without a lookup table. - if (providerId === 'openrouter') { - for (const m of raw) { - if (m.pricing?.prompt == null) continue; - const inputPerM = parseFloat(m.pricing.prompt) * 1_000_000; - openrouterPricingCache.set(m.id, inputPerM); - // Also index by the bare model ID (everything after the first "/") - // so that e.g. "gpt-5-mini" resolves from "openai/gpt-5-mini". - if (m.id.includes('/')) { - const bareId = m.id.slice(m.id.indexOf('/') + 1); - if (!openrouterPricingCache.has(bareId)) { - openrouterPricingCache.set(bareId, inputPerM); - } - } - } - } - - const labelFn = PROVIDER_LABEL_FN[providerId] || ((m) => m.id); - const models = raw.map((m) => ({ - id: m.id, - label: labelFn(m), - provider: providerId, - purpose: inferModelPurpose(m.id), - })); - - providerModelCache.set(cacheKey, { models, lastRefresh: now }); - return models; - } catch (err) { - console.warn(`[Models] Failed to refresh ${providerId} models:`, err.message); - // Always record a lastRefresh so we don't hammer the API on every request. - // Permanent errors (auth/billing/credits) get a longer backoff. - const isPermanent = /401|403|unauthorized|forbidden|credits|spending/i.test(err.message); - const backoff = isPermanent ? 30 * 60 * 1000 : DYNAMIC_REFRESH_INTERVAL; - providerModelCache.set(cacheKey, { - models: existing?.models || [], - lastRefresh: now - DYNAMIC_REFRESH_INTERVAL + backoff, - }); - return existing?.models || []; - } -} - -async function probeOllama(baseUrl, timeoutMs = 1500) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); +async function probeOllama(baseUrl, timeoutMs = 1500, signal = null) { try { - const res = await fetch(`${baseUrl}/api/tags`, { + const { response, text } = await fetchResponseText(`${baseUrl}/api/tags`, { method: 'GET', - signal: controller.signal + maxResponseBytes: 2 * 1024 * 1024, + serviceName: 'Ollama health check', + signal, + timeoutMs, }); - if (!res.ok) { + if (!response.ok) { return { healthy: false, - reason: `Ollama returned HTTP ${res.status}.` + reason: `Ollama returned HTTP ${response.status}.` }; } - const data = await res.json().catch(() => ({})); + let data = {}; + try { data = JSON.parse(text || '{}'); } catch {} const modelCount = Array.isArray(data?.models) ? data.models.length : 0; return { healthy: true, @@ -259,12 +174,11 @@ async function probeOllama(baseUrl, timeoutMs = 1500) { : 'Connected to Ollama, but no local models were reported.' }; } catch (err) { - const reason = err?.name === 'AbortError' + if (isAbortError(err, signal)) throw createAbortError(signal); + const reason = err?.code === 'HTTP_TIMEOUT' ? `Ollama did not respond within ${timeoutMs}ms.` : `Could not reach Ollama at ${baseUrl}.`; return { healthy: false, reason }; - } finally { - clearTimeout(timer); } } @@ -337,11 +251,12 @@ function getProviderCatalog(userId, agentId = null) { }); } -async function getProviderHealthCatalog(userId, agentId = null) { +async function getProviderHealthCatalog(userId, agentId = null, options = {}) { const providers = getProviderCatalog(userId, agentId); const enriched = []; for (const provider of providers) { + throwIfAborted(options.signal); let connected = null; let healthy = provider.available; let degraded = false; @@ -350,7 +265,11 @@ async function getProviderHealthCatalog(userId, agentId = null) { let availabilityReason = provider.availabilityReason; if (provider.id === 'ollama' && provider.enabled) { - const probe = await probeOllama(provider.baseUrl || AI_PROVIDER_DEFINITIONS.ollama.defaultBaseUrl); + const probe = await probeOllama( + provider.baseUrl || AI_PROVIDER_DEFINITIONS.ollama.defaultBaseUrl, + 1500, + options.signal, + ); connected = probe.healthy; healthy = provider.enabled && probe.healthy; degraded = provider.enabled && !probe.healthy; @@ -393,21 +312,30 @@ async function getProviderHealthCatalog(userId, agentId = null) { return enriched; } -async function getSupportedModels(userId, agentId = null) { - const providerCatalog = await getProviderHealthCatalog(userId, agentId); +async function getSupportedModels(userId, agentId = null, options = {}) { + throwIfAborted(options.signal); + const providerCatalog = options.providerCatalog + || await getProviderHealthCatalog(userId, agentId, { signal: options.signal }); const providerById = new Map(providerCatalog.map((provider) => [provider.id, provider])); const all = [...STATIC_MODELS]; - const staticIds = new Set(STATIC_MODELS.map((model) => model.id)); + const seenModelIds = new Set( + STATIC_MODELS.map((model) => createModelSelectionId(model.provider, model.id)), + ); // Ollama: dynamic list from local server const ollama = providerById.get('ollama'); if (ollama?.available) { - const dynamicModels = await refreshDynamicModels(ollama.baseUrl); + const dynamicModels = await refreshOllamaModels({ + baseUrl: ollama.baseUrl || AI_PROVIDER_DEFINITIONS.ollama.defaultBaseUrl, + Provider: OllamaProvider, + signal: options.signal, + }); for (const model of dynamicModels) { - if (!staticIds.has(model.id)) { - all.push(model); - } + const selectionId = createModelSelectionId(model.provider, model.id); + if (seenModelIds.has(selectionId)) continue; + seenModelIds.add(selectionId); + all.push(model); } } @@ -416,16 +344,24 @@ async function getSupportedModels(userId, agentId = null) { .filter((id) => providerById.get(id)?.available) .map(async (id) => { const runtime = getProviderRuntimeConfig(userId, id, agentId); - return refreshProviderModelList(id, runtime.apiKey, runtime.baseUrl); + return refreshProviderModelList({ + providerId: id, + factory: PROVIDER_FACTORIES[id], + apiKey: runtime.apiKey, + baseUrl: runtime.baseUrl, + signal: options.signal, + }); }); const dynamicResults = await Promise.allSettled(dynamicFetches); + throwIfAborted(options.signal); for (const result of dynamicResults) { if (result.status === 'fulfilled') { for (const model of result.value) { - if (!staticIds.has(model.id)) { - all.push(model); - } + const selectionId = createModelSelectionId(model.provider, model.id); + if (seenModelIds.has(selectionId)) continue; + seenModelIds.add(selectionId); + all.push(model); } } } @@ -449,6 +385,7 @@ async function getSupportedModels(userId, agentId = null) { } return all.map((model) => { + const selectableModel = toSelectableModel(model); const provider = providerById.get(model.provider); // Ollama models are always local/free; all others look up the OpenRouter // pricing cache (populated above by Promise.allSettled). @@ -457,20 +394,20 @@ async function getSupportedModels(userId, agentId = null) { : (model.priceTier ?? classifyPriceTier(model.id)); let available = provider?.available !== false; - if (available && globalDisabledSet?.has(model.id)) { + if (available && modelMatchesConfiguredId(selectableModel, globalDisabledSet)) { available = false; } - if (available && planAllowedModels !== null && !planAllowedModels.has(model.id)) { + if (available && planAllowedModels !== null && !modelMatchesConfiguredId(selectableModel, planAllowedModels)) { available = false; } const bareId = model.id.includes('/') ? model.id.slice(model.id.indexOf('/') + 1) : null; const inputCostPerM = model.provider === 'ollama' ? 0 - : (openrouterPricingCache.get(model.id) ?? (bareId ? openrouterPricingCache.get(bareId) : undefined) ?? null); + : (getInputCostPerM(model.id) ?? (bareId ? getInputCostPerM(bareId) : undefined) ?? null); return { - ...model, + ...selectableModel, priceTier, inputCostPerM, available, @@ -480,37 +417,6 @@ async function getSupportedModels(userId, agentId = null) { }); } -async function refreshDynamicModels(baseUrl) { - const cacheKey = baseUrl || AI_PROVIDER_DEFINITIONS.ollama.defaultBaseUrl; - const existing = dynamicModelsByBaseUrl.get(cacheKey); - const now = Date.now(); - - if (existing && now - existing.lastRefresh <= REFRESH_INTERVAL) { - return existing.models; - } - - try { - const ollama = new OllamaProvider({ baseUrl: cacheKey }); - const models = await ollama.listModels(); - const normalized = models.map((name) => ({ - id: name, - label: `${name} (Ollama / Local)`, - provider: 'ollama', - purpose: 'general', - })); - - dynamicModelsByBaseUrl.set(cacheKey, { - models: normalized, - lastRefresh: now - }); - return normalized; - } catch (err) { - console.warn('[Models] Failed to refresh Ollama models:', err.message); - const cached = dynamicModelsByBaseUrl.get(cacheKey); - return cached?.models || []; - } -} - function createProviderInstance(providerStr, userId = null, configOverrides = {}) { const factory = PROVIDER_FACTORIES[providerStr]; if (!factory) { @@ -537,7 +443,6 @@ function createProviderInstance(providerStr, userId = null, configOverrides = {} module.exports = { AI_PROVIDER_DEFINITIONS, PROVIDER_FACTORIES, - SUPPORTED_MODELS: STATIC_MODELS, // Backward compatibility createProviderInstance, getProviderCatalog, getProviderHealthCatalog, diff --git a/server/services/ai/providerRetry.js b/server/services/ai/providerRetry.js index 297116e1..a4f5e71a 100644 --- a/server/services/ai/providerRetry.js +++ b/server/services/ai/providerRetry.js @@ -1,5 +1,16 @@ 'use strict'; +const { createAbortError } = require('../../utils/abort'); +const { + RETRYABLE_HTTP_STATUS: RETRYABLE_STATUS, + RETRYABLE_NETWORK_CODES: RETRYABLE_CODES, + abortableDelay, + computeBackoffMs, + getErrorCode, + getHttpStatus, + retryAfterMilliseconds, +} = require('../../utils/retry'); + // Centralized transient-error retry for AI provider calls. // // A transient blip (rate limit, provider overload, brief network failure) should @@ -14,17 +25,6 @@ const DEFAULTS = { maxDelayMs: 8000, }; -// HTTP statuses worth retrying: request timeout, conflict, rate limit, and the -// 5xx family including Anthropic's 529 "overloaded" and common CDN edge codes. -const RETRYABLE_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504, 520, 521, 522, 524, 529]); - -// Low-level socket / DNS errors surfaced by Node and undici. -const RETRYABLE_CODES = new Set([ - 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN', 'ENOTFOUND', - 'ENETUNREACH', 'EHOSTUNREACH', 'EAGAIN', - 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET', 'UND_ERR_HEADERS_TIMEOUT', -]); - function readNumberEnv(name, fallback, { min = 0, max = Number.MAX_SAFE_INTEGER } = {}) { const raw = process.env[name]; if (raw === undefined || raw === null || String(raw).trim() === '') return fallback; @@ -47,25 +47,10 @@ function resolveConfig(overrides = {}) { // SDKs disagree on where they put the HTTP status: OpenAI/Anthropic expose // `.status`, raw http clients use `.statusCode`, and some nest it under // `.response.status`. Check all of them. -function getStatus(err) { - if (!err || typeof err !== 'object') return null; - const candidates = [err.status, err.statusCode, err.response?.status, err.cause?.status]; - for (const value of candidates) { - const num = Number(value); - if (Number.isFinite(num) && num >= 100 && num < 600) return num; - } - return null; -} - -function getErrorCode(err) { - if (!err || typeof err !== 'object') return null; - return err.code || err.errno || err.cause?.code || null; -} - function isTransientError(err) { if (!err) return false; - const status = getStatus(err); + const status = getHttpStatus(err); if (status !== null) return RETRYABLE_STATUS.has(status); const code = getErrorCode(err); @@ -84,39 +69,11 @@ function isTransientError(err) { // own backoff. Supports both delta-seconds and `retry-after-ms` style headers. function retryAfterMs(err) { if (!err || typeof err !== 'object') return null; - const headers = err.headers || err.response?.headers; - const read = (name) => { - if (!headers) return undefined; - if (typeof headers.get === 'function') return headers.get(name); - return headers[name] ?? headers[name.toLowerCase()]; - }; - - const ms = read('retry-after-ms'); - if (ms !== undefined && ms !== null && String(ms).trim() !== '') { - const parsed = Number(ms); - if (Number.isFinite(parsed) && parsed >= 0) return parsed; - } - - const after = read('retry-after'); - if (after !== undefined && after !== null && String(after).trim() !== '') { - const seconds = Number(after); - if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000; - const date = Date.parse(String(after)); - if (Number.isFinite(date)) return Math.max(0, date - Date.now()); - } - - return null; -} - -// Exponential backoff with equal-jitter: half the window is fixed, half random, -// which spreads retries out without ever collapsing the delay to zero. -function computeBackoffMs(attempt, baseDelayMs, maxDelayMs) { - const exp = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)); - return Math.round(exp / 2 + Math.random() * (exp / 2)); + return retryAfterMilliseconds(err.headers || err.response?.headers); } -function delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); +function abortError(signal) { + return createAbortError(signal, 'Provider retry aborted.'); } /** @@ -137,6 +94,7 @@ async function withProviderRetry(fn, options = {}) { let attempt = 0; while (true) { + if (options.signal?.aborted) throw abortError(options.signal); attempt += 1; try { return await fn(attempt); @@ -153,7 +111,7 @@ async function withProviderRetry(fn, options = {}) { options.onRetry({ attempt, delayMs: waitMs, error: err }); } catch { /* a misbehaving progress callback must not abort the retry */ } } - await delay(waitMs); + await abortableDelay(waitMs, options.signal); } } } diff --git a/server/services/ai/provider_selector.js b/server/services/ai/provider_selector.js new file mode 100644 index 00000000..20acdbac --- /dev/null +++ b/server/services/ai/provider_selector.js @@ -0,0 +1,111 @@ +'use strict'; + +const { getAiSettings } = require('./settings'); +const { createProviderInstance, getSupportedModels } = require('./models'); +const { + getRawModelId, + normalizeModelSelections, + resolveModelSelection, +} = require('./model_identity'); + +function buildSelection(model, userId, providerConfig) { + return { + provider: createProviderInstance(model.provider, userId, providerConfig), + model: getRawModelId(model), + modelSelectionId: model.id, + providerName: model.provider, + }; +} + +async function getProviderForUser( + userId, + _task = '', + isSubagent = false, + modelOverride = null, + providerConfig = {}, +) { + const agentId = providerConfig.agentId || null; + const aiSettings = getAiSettings(userId, agentId); + const models = await getSupportedModels(userId, agentId, { + signal: providerConfig.signal, + }); + const selectableModels = models.filter((model) => model.available !== false); + + if (modelOverride && typeof modelOverride === 'string') { + const requested = resolveModelSelection(selectableModels, modelOverride); + if (!requested) { + throw new Error(`Requested model '${modelOverride.trim()}' is not available.`); + } + return buildSelection(requested, userId, providerConfig); + } + + const configuredEnabledIds = Array.isArray(aiSettings.enabled_models) + ? aiSettings.enabled_models.map((id) => String(id).trim()).filter(Boolean) + : []; + const enabledIds = normalizeModelSelections(selectableModels, configuredEnabledIds); + const availableModels = configuredEnabledIds.length > 0 + ? selectableModels.filter((model) => enabledIds.includes(model.id)) + : selectableModels; + + if (availableModels.length === 0) { + const message = configuredEnabledIds.length > 0 + ? 'None of the enabled AI models are currently available. Check model and provider settings.' + : 'No AI providers are currently available. Open Settings and configure at least one provider.'; + throw new Error(message); + } + + const fallbackModel = availableModels[0]; + const userSelectedDefault = isSubagent + ? (aiSettings.default_subagent_model || 'auto') + : (aiSettings.default_chat_model || 'auto'); + const smarterSelection = aiSettings.smarter_model_selector !== false + && aiSettings.smarter_model_selector !== 'false'; + + let selectedModel = fallbackModel; + if (userSelectedDefault !== 'auto') { + selectedModel = resolveModelSelection(availableModels, userSelectedDefault) || fallbackModel; + } else { + const selectionHint = providerConfig.selectionHint && typeof providerConfig.selectionHint === 'object' + ? providerConfig.selectionHint + : {}; + const preferredPurpose = String(selectionHint.purpose || '').trim().toLowerCase(); + const highAutonomy = selectionHint.autonomyLevel === 'high' + || selectionHint.complexity === 'complex'; + const requiredConfidence = String(selectionHint.requiredConfidence || '').trim().toLowerCase(); + const costMode = String(selectionHint.costMode || aiSettings.cost_mode || 'balanced_auto') + .trim() + .toLowerCase(); + const requestedPurpose = ['planning', 'coding', 'general', 'fast'].includes(preferredPurpose) + ? preferredPurpose + : ''; + const priceRank = { free: 0, cheap: 1, medium: 2, expensive: 3 }; + const chooseForPurpose = (purpose) => { + const candidates = availableModels.filter((model) => model.purpose === purpose); + if (candidates.length === 0) return null; + if (['economy', 'cost_saver', 'lowest_cost'].includes(costMode)) { + return [...candidates].sort((left, right) => ( + (priceRank[left.priceTier] ?? 99) - (priceRank[right.priceTier] ?? 99) + ))[0]; + } + if (['quality', 'highest_quality'].includes(costMode) || requiredConfidence === 'high') { + return candidates.find((model) => model.priceTier !== 'free' && model.priceTier !== 'cheap') + || candidates[0]; + } + return candidates[0]; + }; + + if (smarterSelection && requestedPurpose) { + selectedModel = chooseForPurpose(requestedPurpose) || fallbackModel; + } else if (smarterSelection && highAutonomy) { + selectedModel = chooseForPurpose('planning') || chooseForPurpose('general') || fallbackModel; + } else if (isSubagent) { + selectedModel = chooseForPurpose('fast') || fallbackModel; + } else { + selectedModel = chooseForPurpose('general') || fallbackModel; + } + } + + return buildSelection(selectedModel, userId, providerConfig); +} + +module.exports = { getProviderForUser }; diff --git a/server/services/ai/providers/anthropic.js b/server/services/ai/providers/anthropic.js index 170b5166..5f1d1c5f 100644 --- a/server/services/ai/providers/anthropic.js +++ b/server/services/ai/providers/anthropic.js @@ -31,8 +31,8 @@ class AnthropicProvider extends BaseProvider { }); } - async listModels() { - const res = await this.client.models.list({ limit: 100 }); + async listModels(signal = null) { + const res = await this.client.models.list({ limit: 100 }, { signal }); return (res.data || []).map((m) => ({ id: m.id, name: m.display_name || m.id })); } diff --git a/server/services/ai/providers/claudeCode.js b/server/services/ai/providers/claudeCode.js index e28d079d..93002db6 100644 --- a/server/services/ai/providers/claudeCode.js +++ b/server/services/ai/providers/claudeCode.js @@ -2,6 +2,8 @@ const os = require('os'); const fs = require('fs'); const path = require('path'); const Anthropic = require('@anthropic-ai/sdk'); +const { ENV_FILE, upsertEnvValue } = require('../../../../runtime/paths'); +const { fetchResponseText } = require('../../network/http'); const { AnthropicProvider } = require('./anthropic'); const CLAUDE_CLI_CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json'); @@ -12,6 +14,8 @@ const CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI const CLAUDE_CODE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e'; const CLAUDE_CODE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token'; const CLAUDE_CODE_SCOPES = 'user:inference user:profile org:create_api_key user:sessions:claude_code user:mcp_servers'; +const OAUTH_REFRESH_TIMEOUT_MS = 30000; +const OAUTH_MAX_RESPONSE_BYTES = 256 * 1024; function readTokenRecord(data) { const tokens = data?.claudeAiOauthTokens || data?.claudeAiOauth || {}; @@ -71,40 +75,17 @@ function normalizeExpiresAt(data) { return null; } -function sanitizeEnvKey(key) { - return String(key).replace(/[\r\n]/g, ''); -} - -function sanitizeEnvValue(value) { - return String(value).replace(/[\r\n]/g, ''); -} - function persistEnvValue(key, value) { if (!value) return; try { - const { ENV_FILE } = require('../../../../runtime/paths'); - const safeKey = sanitizeEnvKey(key); - const safeValue = sanitizeEnvValue(value); - const raw = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, 'utf8') : ''; - const lines = raw ? raw.split('\n') : []; - let replaced = false; - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith(`${safeKey}=`)) { - lines[i] = `${safeKey}=${safeValue}`; - replaced = true; - break; - } - } - if (!replaced) lines.push(`${safeKey}=${safeValue}`); - const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n'; - fs.mkdirSync(path.dirname(ENV_FILE), { recursive: true }); - fs.writeFileSync(ENV_FILE, output, { mode: 0o600 }); + upsertEnvValue(ENV_FILE, key, value); } catch { } } async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch, signal = null) { if (!refreshToken) return null; - const response = await fetchImpl(CLAUDE_CODE_TOKEN_URL, { + const { response, text } = await fetchResponseText(CLAUDE_CODE_TOKEN_URL, { + fetchImpl, method: 'POST', headers: { 'Content-Type': 'application/json', @@ -117,9 +98,13 @@ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch, sig client_id: CLAUDE_CODE_CLIENT_ID, }), signal, + timeoutMs: OAUTH_REFRESH_TIMEOUT_MS, + maxResponseBytes: OAUTH_MAX_RESPONSE_BYTES, + serviceName: 'Claude Code OAuth refresh', + timeoutCode: 'PROVIDER_OAUTH_TIMEOUT', + tooLargeCode: 'PROVIDER_OAUTH_RESPONSE_TOO_LARGE', }); - const text = await response.text(); let data = {}; try { data = text ? JSON.parse(text) : {}; @@ -128,7 +113,9 @@ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch, sig } if (!response.ok) { - const detail = data?.error?.message || data?.error_description || data?.error || text || 'Unknown error'; + const detail = String( + data?.error?.message || data?.error_description || data?.error || text || 'Unknown error', + ).slice(0, 2000); throw new Error(`Claude Code OAuth refresh failed: HTTP ${response.status} ${detail}`); } if (!data.access_token) { diff --git a/server/services/ai/providers/githubCopilot.js b/server/services/ai/providers/githubCopilot.js index 28ef3734..2ea5a5e3 100644 --- a/server/services/ai/providers/githubCopilot.js +++ b/server/services/ai/providers/githubCopilot.js @@ -1,4 +1,9 @@ const { OpenAIProvider } = require('./openai'); +const { + fetchResponseText, + waitForAbortableResult, +} = require('../../network/http'); +const { sanitizeProviderErrorDetail } = require('./provider_error'); class GithubCopilotProvider extends OpenAIProvider { constructor(config = {}) { @@ -27,35 +32,46 @@ class GithubCopilotProvider extends OpenAIProvider { } async _refreshCopilotToken(signal = null) { - if (this._refreshPromise) return this._refreshPromise; - const now = Math.floor(Date.now() / 1000); // Refresh token if missing or expiring in less than 5 minutes if (this.copilotToken && this.tokenExpiresAt >= now + 300) { return; } - this._refreshPromise = (async () => { - try { + if (!this._refreshPromise) { + this._refreshPromise = (async () => { if (!this.githubToken) { throw new Error('GitHub Copilot access token is missing. Please run `neoagent login github-copilot`.'); } - const res = await fetch('https://api.github.com/copilot_internal/v2/token', { + const { response, text } = await fetchResponseText( + 'https://api.github.com/copilot_internal/v2/token', + { headers: { 'Authorization': `token ${this.githubToken}`, 'Accept': 'application/json', 'User-Agent': 'NeoAgent/1.0.0' }, - signal, - }); - - if (!res.ok) { - const errorText = await res.text().catch(() => 'Unknown error'); - throw new Error(`Failed to refresh GitHub Copilot token: HTTP ${res.status} - ${errorText}`); + maxResponseBytes: 1024 * 1024, + serviceName: 'GitHub Copilot token refresh', + }, + ); + + if (!response.ok) { + const error = new Error( + `Failed to refresh GitHub Copilot token: HTTP ${response.status} - ${sanitizeProviderErrorDetail(text.slice(0, 500))}`, + ); + error.status = response.status; + error.headers = response.headers; + throw error; } - const data = await res.json(); + let data; + try { + data = JSON.parse(text || '{}'); + } catch { + throw new Error('Invalid token response from GitHub Copilot.'); + } if (!data || typeof data.token !== 'string' || !data.token) { throw new Error('Invalid token response from GitHub Copilot.'); } @@ -71,12 +87,16 @@ class GithubCopilotProvider extends OpenAIProvider { // Update the client's API key this.client.apiKey = this.copilotToken; - } finally { + })().finally(() => { this._refreshPromise = null; - } - })(); + }); + } - return this._refreshPromise; + return waitForAbortableResult( + this._refreshPromise, + signal, + 'GitHub Copilot token refresh aborted.', + ); } async chat(messages, tools = [], options = {}) { diff --git a/server/services/ai/providers/google.js b/server/services/ai/providers/google.js index 82d234ba..0bf94653 100644 --- a/server/services/ai/providers/google.js +++ b/server/services/ai/providers/google.js @@ -1,5 +1,6 @@ const { GoogleGenerativeAI } = require('@google/generative-ai'); const { BaseProvider } = require('./base'); +const { fetchResponseText } = require('../../network/http'); class GoogleProvider extends BaseProvider { constructor(config = {}) { @@ -31,13 +32,30 @@ class GoogleProvider extends BaseProvider { this.genAI = new GoogleGenerativeAI(this.apiKey); } - async listModels() { + async listModels(signal = null) { const DROP = /tts|lyria|robotics|deep-research|antigravity|computer-use|-image(?!.*it)/i; - const res = await fetch( - `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}&pageSize=200` + const { response, text } = await fetchResponseText( + 'https://generativelanguage.googleapis.com/v1beta/models?pageSize=200', + { + headers: { 'x-goog-api-key': this.apiKey }, + maxResponseBytes: 5 * 1024 * 1024, + serviceName: 'Google model catalog', + signal, + }, ); - if (!res.ok) throw new Error(`Google models API returned ${res.status}`); - const { models = [] } = await res.json(); + if (!response.ok) { + const error = new Error(`Google models API returned ${response.status}`); + error.status = response.status; + error.headers = response.headers; + throw error; + } + let payload; + try { + payload = JSON.parse(text || '{}'); + } catch { + throw new Error('Google models API returned invalid JSON.'); + } + const { models = [] } = payload; return models .filter((m) => { const id = m.name.replace('models/', ''); diff --git a/server/services/ai/providers/grok.js b/server/services/ai/providers/grok.js index 08c485bc..3258483d 100644 --- a/server/services/ai/providers/grok.js +++ b/server/services/ai/providers/grok.js @@ -1,5 +1,6 @@ const OpenAI = require('openai'); const { OpenAICompatibleProvider } = require('./openaiCompatible'); +const { wrapProviderError } = require('./provider_error'); class GrokProvider extends OpenAICompatibleProvider { constructor(config = {}) { @@ -11,15 +12,15 @@ class GrokProvider extends OpenAICompatibleProvider { }); } - async listModels() { + async listModels(signal = null) { try { - const res = await this.client.models.list(); + const res = await this.client.models.list({ signal }); const DROP = /imagine|diffus|embed|-tts/i; return res.data .filter((m) => !DROP.test(m.id)) .map((m) => ({ id: m.id, name: m.id })); } catch (err) { - throw new Error(`Failed to list Grok models: ${err.message || String(err)}`); + throw wrapProviderError(err, 'Failed to list Grok models', { signal }); } } diff --git a/server/services/ai/providers/grokOauth.js b/server/services/ai/providers/grokOauth.js index 00ce9db5..3f7f6862 100644 --- a/server/services/ai/providers/grokOauth.js +++ b/server/services/ai/providers/grokOauth.js @@ -1,12 +1,14 @@ -const fs = require('fs'); -const path = require('path'); const OpenAI = require('openai'); +const { ENV_FILE, upsertEnvValue } = require('../../../../runtime/paths'); +const { fetchResponseText } = require('../../network/http'); const { GrokProvider } = require('./grok'); const GROK_OAUTH_BASE_URL = 'https://api.x.ai/v1'; const GROK_OAUTH_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828'; const GROK_OAUTH_TOKEN_URL = 'https://auth.x.ai/oauth2/token'; const GROK_OAUTH_SCOPES = 'openid profile email offline_access grok-cli:access api:access'; +const OAUTH_REFRESH_TIMEOUT_MS = 30000; +const OAUTH_MAX_RESPONSE_BYTES = 256 * 1024; function normalizeExpiresAt(data) { if (typeof data.expires_at === 'number' && Number.isFinite(data.expires_at)) { @@ -21,29 +23,14 @@ function normalizeExpiresAt(data) { function persistEnvValue(key, value) { if (!value) return; try { - const { ENV_FILE } = require('../../../../runtime/paths'); - const safeKey = String(key).replace(/[\r\n]/g, ''); - const safeValue = String(value).replace(/[\r\n]/g, ''); - const raw = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, 'utf8') : ''; - const lines = raw ? raw.split('\n') : []; - let replaced = false; - for (let i = 0; i < lines.length; i++) { - if (lines[i].startsWith(`${safeKey}=`)) { - lines[i] = `${safeKey}=${safeValue}`; - replaced = true; - break; - } - } - if (!replaced) lines.push(`${safeKey}=${safeValue}`); - const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n'; - fs.mkdirSync(path.dirname(ENV_FILE), { recursive: true }); - fs.writeFileSync(ENV_FILE, output, { mode: 0o600 }); + upsertEnvValue(ENV_FILE, key, value); } catch { } } async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch, signal = null) { if (!refreshToken) return null; - const response = await fetchImpl(GROK_OAUTH_TOKEN_URL, { + const { response, text } = await fetchResponseText(GROK_OAUTH_TOKEN_URL, { + fetchImpl, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -55,9 +42,13 @@ async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch, sign client_id: GROK_OAUTH_CLIENT_ID, }), signal, + timeoutMs: OAUTH_REFRESH_TIMEOUT_MS, + maxResponseBytes: OAUTH_MAX_RESPONSE_BYTES, + serviceName: 'Grok OAuth refresh', + timeoutCode: 'PROVIDER_OAUTH_TIMEOUT', + tooLargeCode: 'PROVIDER_OAUTH_RESPONSE_TOO_LARGE', }); - const text = await response.text(); let data = {}; try { data = text ? JSON.parse(text) : {}; @@ -69,7 +60,7 @@ async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch, sign if (data?.error === 'invalid_grant') { throw new Error('Grok OAuth refresh token is invalid or expired. Re-run `neoagent login grok-oauth` to re-authenticate.'); } - const detail = data?.error_description || data?.error || text || 'Unknown error'; + const detail = String(data?.error_description || data?.error || text || 'Unknown error').slice(0, 2000); throw new Error(`Grok OAuth refresh failed: HTTP ${response.status} ${detail}`); } if (!data.access_token) { diff --git a/server/services/ai/providers/nvidia.js b/server/services/ai/providers/nvidia.js index 07fdd015..ae47e70e 100644 --- a/server/services/ai/providers/nvidia.js +++ b/server/services/ai/providers/nvidia.js @@ -1,5 +1,6 @@ const OpenAI = require('openai'); const { OpenAICompatibleProvider } = require('./openaiCompatible'); +const { wrapProviderError } = require('./provider_error'); const NVIDIA_BASE_URL = 'https://integrate.api.nvidia.com/v1'; @@ -32,15 +33,15 @@ class NvidiaProvider extends OpenAICompatibleProvider { }); } - async listModels() { + async listModels(signal = null) { try { - const res = await this.client.models.list(); + const res = await this.client.models.list({ signal }); const DROP = /embed|bge|e5-|rerank|guard|safety|moderat|diffus|flux|stable|imagen|vision-enc|whisper|tts|speech|paraphrase|classif/i; return res.data .filter((m) => !DROP.test(m.id)) .map((m) => ({ id: m.id, name: m.id })); } catch (err) { - throw new Error(`NVIDIA NIM request failed: ${err?.message || String(err)}`); + throw wrapProviderError(err, 'NVIDIA NIM request failed', { signal }); } } @@ -78,7 +79,9 @@ class NvidiaProvider extends OpenAICompatibleProvider { try { response = await this.client.chat.completions.create(params, { signal: options.signal }); } catch (err) { - throw new Error(`NVIDIA NIM request failed: ${err?.message || String(err)}`); + throw wrapProviderError(err, 'NVIDIA NIM request failed', { + signal: options.signal, + }); } return this.normalizeResponse(response); } @@ -95,7 +98,9 @@ class NvidiaProvider extends OpenAICompatibleProvider { try { stream = await this.client.chat.completions.create(params, { signal: options.signal }); } catch (err) { - throw new Error(`NVIDIA NIM request failed: ${err?.message || String(err)}`); + throw wrapProviderError(err, 'NVIDIA NIM request failed', { + signal: options.signal, + }); } let toolCalls = []; diff --git a/server/services/ai/providers/ollama.js b/server/services/ai/providers/ollama.js index a8565578..df985edf 100644 --- a/server/services/ai/providers/ollama.js +++ b/server/services/ai/providers/ollama.js @@ -1,4 +1,24 @@ +'use strict'; + const { BaseProvider } = require('./base'); +const { fetchResponseText, readResponseText } = require('../../network/http'); +const { createAbortError, isAbortError, throwIfAborted } = require('../../../utils/abort'); +const { readOllamaStream } = require('./ollama_stream'); + +const MAX_CHAT_RESPONSE_BYTES = 16 * 1024 * 1024; + +function ollamaError(message, status = null) { + const error = new Error( + status == null + ? `Ollama request failed: ${message}` + : `Ollama request failed (HTTP ${status}): ${message}`, + ); + if (status != null) error.status = status; + if (/does not support tools|tools.*not supported/i.test(String(message || ''))) { + error.code = 'OLLAMA_TOOLS_UNSUPPORTED'; + } + return error; +} class OllamaProvider extends BaseProvider { constructor(config = {}) { @@ -9,22 +29,20 @@ class OllamaProvider extends BaseProvider { } async listModels(signal = null) { - const controller = new AbortController(); - const abortFromParent = () => controller.abort(signal?.reason); - if (signal?.aborted) abortFromParent(); - else signal?.addEventListener('abort', abortFromParent, { once: true }); - const timer = setTimeout(() => controller.abort(), 5000); try { - const res = await fetch(`${this.baseUrl}/api/tags`, { signal: controller.signal }); - const data = await res.json(); + const { response, text } = await fetchResponseText(`${this.baseUrl}/api/tags`, { + maxResponseBytes: 2 * 1024 * 1024, + serviceName: 'Ollama model catalog', + signal, + timeoutMs: 5000, + }); + if (!response.ok) throw new Error(`Ollama /api/tags returned HTTP ${response.status}`); + const data = JSON.parse(text || '{}'); this.models = (data.models || []).map(m => m.name); return this.models; } catch (err) { - if (signal?.aborted) throw err; + if (isAbortError(err, signal)) throw createAbortError(signal); return []; - } finally { - clearTimeout(timer); - signal?.removeEventListener('abort', abortFromParent); } } @@ -45,13 +63,22 @@ class OllamaProvider extends BaseProvider { message: `Downloading local Ollama model '${model}'. First-time pulls can take a while.` }); try { - const res = await fetch(`${this.baseUrl}/api/pull`, { + const { response, text } = await fetchResponseText(`${this.baseUrl}/api/pull`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: model, stream: false }), + maxResponseBytes: 2 * 1024 * 1024, + serviceName: 'Ollama model pull', signal, + timeoutMs: 5 * 60 * 1000, }); - if (!res.ok) throw new Error(`Pull failed: ${res.statusText}`); + if (!response.ok) { + let detail = text; + try { detail = JSON.parse(text || '{}').error || text; } catch {} + throw new Error( + `Pull failed with HTTP ${response.status}: ${String(detail || response.statusText).slice(0, 500)}`, + ); + } console.log(`[Ollama] Model '${model}' pulled successfully.`); this.onStatus?.({ kind: 'model_download', @@ -64,6 +91,7 @@ class OllamaProvider extends BaseProvider { await this.listModels(signal); return true; } catch (e) { + if (isAbortError(e, signal)) throw createAbortError(signal); this.onStatus?.({ kind: 'model_download', status: 'failed', @@ -137,35 +165,55 @@ class OllamaProvider extends BaseProvider { signal, }); if (!res.ok) { - const detail = await res.text().catch(() => ''); + const detail = await readResponseText(res, { + maxResponseBytes: 64 * 1024, + serviceName: 'Ollama chat error', + }).catch(() => ''); let message = detail; try { message = JSON.parse(detail)?.error || detail; } catch {} - const err = new Error(`Ollama /api/chat failed (HTTP ${res.status}): ${message || res.statusText}`); - if (/does not support tools|tools.*not supported/i.test(message)) { - err.code = 'OLLAMA_TOOLS_UNSUPPORTED'; - } - throw err; + throw ollamaError(message || res.statusText, res.status); } return res; } + async readChatResponse(body, signal = null) { + const res = await this.postChat(body, signal); + const text = await readResponseText(res, { + maxResponseBytes: MAX_CHAT_RESPONSE_BYTES, + serviceName: 'Ollama chat', + }); + throwIfAborted(signal, 'Ollama chat aborted.'); + let data; + try { + data = JSON.parse(text); + } catch (cause) { + throw new Error('Ollama /api/chat returned malformed JSON.', { cause }); + } + if (data.error) throw ollamaError(data.error); + return data; + } + async chat(messages, tools = [], options = {}) { const model = options.model || this.config.model || 'llama3.1'; await this.ensureModel(model, options.signal); - let res; + let data; try { - res = await this.postChat(this.buildChatBody(messages, tools, { ...options, model }, false), options.signal); + data = await this.readChatResponse( + this.buildChatBody(messages, tools, { ...options, model }, false), + options.signal, + ); } catch (err) { if (err.code === 'OLLAMA_TOOLS_UNSUPPORTED' && tools.length > 0) { console.warn(`[Ollama] Model '${model}' does not support tools; retrying without them.`); - res = await this.postChat(this.buildChatBody(messages, [], { ...options, model }, false), options.signal); + data = await this.readChatResponse( + this.buildChatBody(messages, [], { ...options, model }, false), + options.signal, + ); } else { throw err; } } - - const data = await res.json(); const msg = data.message || {}; return { @@ -192,65 +240,37 @@ class OllamaProvider extends BaseProvider { const model = options.model || this.config.model || 'llama3.1'; await this.ensureModel(model, options.signal); - let res; - try { - res = await this.postChat(this.buildChatBody(messages, tools, { ...options, model }, true), options.signal); - } catch (err) { - if (err.code === 'OLLAMA_TOOLS_UNSUPPORTED' && tools.length > 0) { - console.warn(`[Ollama] Model '${model}' does not support tools; retrying stream without them.`); - res = await this.postChat(this.buildChatBody(messages, [], { ...options, model }, true), options.signal); - } else { - throw err; - } - } - - const reader = res.body.getReader(); - const decoder = new TextDecoder(); - let content = ''; - let buffer = ''; - let accumulatedToolCalls = []; - + let requestTools = tools; + let retriedWithoutTools = false; while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split('\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - if (!line.trim()) continue; - try { - const data = JSON.parse(line); - if (data.message?.content) { - content += data.message.content; - yield { type: 'content', content: data.message.content }; - } - if (data.message?.tool_calls && Array.isArray(data.message.tool_calls)) { - const mapped = data.message.tool_calls.map((tc, i) => ({ - id: `call_ollama_${Date.now()}_${i}`, - type: 'function', - function: { - name: tc.function.name, - arguments: typeof tc.function.arguments === 'string' ? tc.function.arguments : JSON.stringify(tc.function.arguments || {}) - } - })); - accumulatedToolCalls = accumulatedToolCalls.concat(mapped); - } - if (data.done) { - yield { - type: 'done', - content, - toolCalls: accumulatedToolCalls, - finishReason: accumulatedToolCalls.length > 0 ? 'tool_calls' : 'stop', - usage: data.prompt_eval_count ? { - promptTokens: data.prompt_eval_count || 0, - completionTokens: data.eval_count || 0, - totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0) - } : null - }; - } - } catch {} + let emittedChunk = false; + try { + const res = await this.postChat( + this.buildChatBody(messages, requestTools, { ...options, model }, true), + options.signal, + ); + for await (const chunk of readOllamaStream(res, { + errorFactory: ollamaError, + maxResponseBytes: MAX_CHAT_RESPONSE_BYTES, + signal: options.signal, + })) { + emittedChunk = true; + yield chunk; + } + return; + } catch (err) { + if ( + err.code === 'OLLAMA_TOOLS_UNSUPPORTED' + && tools.length > 0 + && !retriedWithoutTools + && !emittedChunk + ) { + console.warn(`[Ollama] Model '${model}' does not support tools; retrying stream without them.`); + retriedWithoutTools = true; + requestTools = []; + continue; + } + throw err; } } } diff --git a/server/services/ai/providers/ollama_stream.js b/server/services/ai/providers/ollama_stream.js new file mode 100644 index 00000000..8a228ca6 --- /dev/null +++ b/server/services/ai/providers/ollama_stream.js @@ -0,0 +1,142 @@ +'use strict'; + +const { waitForAbortableResult } = require('../../network/http'); +const { throwIfAborted } = require('../../../utils/abort'); + +function malformedStreamError(cause = null) { + const error = new Error('Ollama /api/chat returned malformed streaming JSON.', cause ? { cause } : undefined); + error.code = 'OLLAMA_STREAM_MALFORMED'; + return error; +} + +function streamEndedEarlyError() { + const error = new Error('Ollama stream ended before sending a completion marker.'); + error.code = 'OLLAMA_STREAM_INCOMPLETE'; + return error; +} + +function parseLine(line) { + if (!line.trim()) return null; + try { + const value = JSON.parse(line); + if (!value || typeof value !== 'object' || Array.isArray(value)) { + throw malformedStreamError(); + } + return value; + } catch (error) { + if (error?.code === 'OLLAMA_STREAM_MALFORMED') throw error; + throw malformedStreamError(error); + } +} + +function usageFrom(data) { + if (data.prompt_eval_count == null && data.eval_count == null) return null; + const promptTokens = Number(data.prompt_eval_count) || 0; + const completionTokens = Number(data.eval_count) || 0; + return { + promptTokens, + completionTokens, + totalTokens: promptTokens + completionTokens, + }; +} + +function consumeData(data, state, errorFactory) { + if (data.error) throw errorFactory(data.error); + const chunks = []; + if (data.message?.content) { + const content = String(data.message.content); + state.content += content; + chunks.push({ type: 'content', content }); + } + if (Array.isArray(data.message?.tool_calls)) { + for (const toolCall of data.message.tool_calls) { + const name = String(toolCall?.function?.name || '').trim(); + if (!name) throw malformedStreamError(); + state.toolCalls.push({ + id: `call_ollama_${state.callSeed}_${state.nextCallIndex}`, + type: 'function', + function: { + name, + arguments: typeof toolCall.function.arguments === 'string' + ? toolCall.function.arguments + : JSON.stringify(toolCall.function.arguments || {}), + }, + }); + state.nextCallIndex += 1; + } + } + if (data.done === true) { + state.done = true; + chunks.push({ + type: 'done', + content: state.content, + toolCalls: state.toolCalls, + finishReason: state.toolCalls.length > 0 ? 'tool_calls' : 'stop', + usage: usageFrom(data), + }); + } + return chunks; +} + +async function* readOllamaStream(response, options = {}) { + const reader = response?.body?.getReader?.(); + if (!reader) throw new Error('Ollama /api/chat returned no streaming response body.'); + const decoder = new TextDecoder(); + const maxResponseBytes = Number(options.maxResponseBytes) > 0 + ? Number(options.maxResponseBytes) + : 16 * 1024 * 1024; + const errorFactory = options.errorFactory || ((message) => new Error(String(message || 'Ollama error'))); + const state = { + callSeed: Date.now(), + content: '', + done: false, + nextCallIndex: 0, + toolCalls: [], + }; + let buffer = ''; + let responseBytes = 0; + + const emitLine = (line) => { + const data = parseLine(line); + return data ? consumeData(data, state, errorFactory) : []; + }; + + try { + while (!state.done) { + throwIfAborted(options.signal, 'Ollama stream aborted.'); + const { done, value } = await waitForAbortableResult( + reader.read(), + options.signal, + 'Ollama stream aborted.', + ); + if (done) break; + responseBytes += value?.byteLength || 0; + if (responseBytes > maxResponseBytes) { + const error = new Error('Ollama stream exceeded its response safety limit.'); + error.code = 'HTTP_RESPONSE_TOO_LARGE'; + throw error; + } + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + for (const line of lines) { + for (const chunk of emitLine(line)) yield chunk; + if (state.done) return; + } + } + + buffer += decoder.decode(); + if (buffer.trim()) { + for (const chunk of emitLine(buffer)) yield chunk; + } + if (!state.done) throw streamEndedEarlyError(); + } finally { + await reader.cancel().catch(() => {}); + reader.releaseLock?.(); + } +} + +module.exports = { + readOllamaStream, +}; diff --git a/server/services/ai/providers/openai.js b/server/services/ai/providers/openai.js index f3af8bbd..a653ebf9 100644 --- a/server/services/ai/providers/openai.js +++ b/server/services/ai/providers/openai.js @@ -1,12 +1,20 @@ const OpenAI = require('openai'); const { OpenAICompatibleProvider } = require('./openaiCompatible'); +const { wrapProviderError } = require('./provider_error'); class OpenAIProvider extends OpenAICompatibleProvider { constructor(config = {}) { super(config); this.name = 'openai'; this.models = [ + 'gpt-5.6', + 'gpt-5.6-sol', + 'gpt-5.6-terra', + 'gpt-5.6-luna', 'gpt-5.5', + 'gpt-5.4', + 'gpt-5.4-mini', + 'gpt-5.4-nano', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', @@ -16,9 +24,35 @@ class OpenAIProvider extends OpenAICompatibleProvider { 'o4-mini' ]; // Reasoning models: no temperature, use max_completion_tokens, support reasoning_effort - this.reasoningModels = new Set(['gpt-5.5', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5.2', 'gpt-5.1', 'o1', 'o3', 'o3-pro', 'o4-mini', 'o3-mini']); + this.reasoningModels = new Set([ + 'gpt-5.6', + 'gpt-5.6-sol', + 'gpt-5.6-terra', + 'gpt-5.6-luna', + 'gpt-5.5', + 'gpt-5.4', + 'gpt-5.4-mini', + 'gpt-5.4-nano', + 'gpt-5', + 'gpt-5-mini', + 'gpt-5-nano', + 'gpt-5.2', + 'gpt-5.1', + 'o1', + 'o3', + 'o3-pro', + 'o4-mini', + 'o3-mini', + ]); this.contextWindows = { - 'gpt-5.5': 1000000, + 'gpt-5.6': 1050000, + 'gpt-5.6-sol': 1050000, + 'gpt-5.6-terra': 1050000, + 'gpt-5.6-luna': 1050000, + 'gpt-5.5': 1050000, + 'gpt-5.4': 1050000, + 'gpt-5.4-mini': 400000, + 'gpt-5.4-nano': 400000, 'gpt-5': 400000, 'gpt-5-mini': 400000, 'gpt-5-nano': 128000, @@ -36,15 +70,15 @@ class OpenAIProvider extends OpenAICompatibleProvider { }); } - async listModels() { + async listModels(signal = null) { try { - const res = await this.client.models.list(); + const res = await this.client.models.list({ signal }); const DROP = /dall-e|whisper|tts|embed|moderat|realtime|audio|transcribe|search-api|-image-|babbage|davinci-002|^sora|-instruct/i; return res.data .filter((m) => !DROP.test(m.id)) .map((m) => ({ id: m.id, name: m.id })); } catch (err) { - throw new Error(`Failed to list OpenAI models: ${err.message || String(err)}`); + throw wrapProviderError(err, 'Failed to list OpenAI models', { signal }); } } diff --git a/server/services/ai/providers/openaiCodex.js b/server/services/ai/providers/openaiCodex.js index 8dad0f4a..6a8336bf 100644 --- a/server/services/ai/providers/openaiCodex.js +++ b/server/services/ai/providers/openaiCodex.js @@ -1,6 +1,9 @@ +'use strict'; + const crypto = require('crypto'); const OpenAI = require('openai'); const { BaseProvider } = require('./base'); +const { wrapProviderError } = require('./provider_error'); const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex'; const OPENAI_CODEX_EMPTY_INPUT_TEXT = ' '; @@ -439,8 +442,10 @@ class OpenAICodexProvider extends BaseProvider { { headers: this._requestHeaders(), signal: options.signal }, ); } catch (err) { - if (options.signal?.aborted) throw err; - throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`); + throw wrapProviderError(err, 'OpenAI Codex request failed', { + detail: formatOpenAIError(err), + signal: options.signal, + }); } const toolCalls = extractToolCalls(response); @@ -468,8 +473,10 @@ class OpenAICodexProvider extends BaseProvider { { headers: this._requestHeaders(), signal: options.signal }, ); } catch (err) { - if (options.signal?.aborted) throw err; - throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`); + throw wrapProviderError(err, 'OpenAI Codex request failed', { + detail: formatOpenAIError(err), + signal: options.signal, + }); } let content = ''; diff --git a/server/services/ai/providers/openrouter.js b/server/services/ai/providers/openrouter.js index 40190746..9ca89c12 100644 --- a/server/services/ai/providers/openrouter.js +++ b/server/services/ai/providers/openrouter.js @@ -1,5 +1,7 @@ const OpenAI = require('openai'); const { OpenAICompatibleProvider } = require('./openaiCompatible'); +const { fetchResponseText } = require('../../network/http'); +const { wrapProviderError } = require('./provider_error'); const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1'; @@ -25,12 +27,26 @@ class OpenRouterProvider extends OpenAICompatibleProvider { }); } - async listModels() { - const res = await fetch(`${this.baseURL}/models`, { + async listModels(signal = null) { + const { response, text } = await fetchResponseText(`${this.baseURL}/models`, { headers: { 'Authorization': `Bearer ${this.client.apiKey}` }, + maxResponseBytes: 5 * 1024 * 1024, + serviceName: 'OpenRouter model catalog', + signal, }); - if (!res.ok) throw new Error(`OpenRouter /models returned HTTP ${res.status}`); - const { data } = await res.json(); + if (!response.ok) { + const error = new Error(`OpenRouter /models returned HTTP ${response.status}`); + error.status = response.status; + error.headers = response.headers; + throw error; + } + let payload; + try { + payload = JSON.parse(text || '{}'); + } catch { + throw new Error('OpenRouter /models returned invalid JSON.'); + } + const { data } = payload; const models = data || []; for (const m of models) { if (m.context_length) contextWindowCache.set(m.id, m.context_length); @@ -74,7 +90,9 @@ class OpenRouterProvider extends OpenAICompatibleProvider { try { response = await this.client.chat.completions.create(params, { signal: options.signal }); } catch (err) { - throw new Error(`OpenRouter request failed: ${err?.message || String(err)}`); + throw wrapProviderError(err, 'OpenRouter request failed', { + signal: options.signal, + }); } // OpenRouter returns HTTP 200 even for errors (rate limits, model unavailable, etc.) const orErr = this._extractOpenRouterError(response); @@ -97,7 +115,9 @@ class OpenRouterProvider extends OpenAICompatibleProvider { try { stream = await this.client.chat.completions.create(params, { signal: options.signal }); } catch (err) { - throw new Error(`OpenRouter request failed: ${err?.message || String(err)}`); + throw wrapProviderError(err, 'OpenRouter request failed', { + signal: options.signal, + }); } let toolCalls = []; @@ -148,7 +168,9 @@ class OpenRouterProvider extends OpenAICompatibleProvider { } } catch (err) { // Re-throw SDK APIErrors (from OpenRouter SSE error events) with OpenRouter context. - throw new Error(`OpenRouter: ${err?.message || String(err)}`); + throw wrapProviderError(err, 'OpenRouter stream failed', { + signal: options.signal, + }); } if (toolCalls.length > 0) { diff --git a/server/services/ai/providers/provider_error.js b/server/services/ai/providers/provider_error.js new file mode 100644 index 00000000..40a27d86 --- /dev/null +++ b/server/services/ai/providers/provider_error.js @@ -0,0 +1,36 @@ +'use strict'; + +const { createAbortError, isAbortError } = require('../../../utils/abort'); + +function sanitizeProviderErrorDetail(value) { + return String(value || 'Unknown provider error') + .slice(0, 2000) + .replace(/\b(Bearer|Basic|token)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 [redacted]') + .replace(/([?&](?:key|api_key|access_token)=)[^&\s]+/gi, '$1[redacted]') + .replace( + /\b(api[_-]?key|access_token|refresh_token|authorization)\b\s*[:=]\s*["']?[^\s,"'}\]]+/gi, + '$1=[redacted]', + ); +} + +function wrapProviderError(error, prefix, options = {}) { + if (options.signal?.aborted) return createAbortError(options.signal); + if (isAbortError(error)) return error; + + const detail = typeof options.detail === 'string' + ? options.detail + : error?.message || String(error); + const wrapped = new Error( + `${prefix}: ${sanitizeProviderErrorDetail(detail)}`, + { cause: error }, + ); + for (const property of ['status', 'statusCode', 'code', 'headers', 'response', 'type']) { + if (error?.[property] !== undefined) wrapped[property] = error[property]; + } + return wrapped; +} + +module.exports = { + sanitizeProviderErrorDetail, + wrapProviderError, +}; diff --git a/server/services/ai/settings.js b/server/services/ai/settings.js index 4f204506..8dc39f9b 100644 --- a/server/services/ai/settings.js +++ b/server/services/ai/settings.js @@ -12,7 +12,7 @@ const AI_PROVIDER_DEFINITIONS = Object.freeze({ openai: { id: 'openai', label: 'OpenAI', - description: 'GPT-5 and GPT-4.1 models for fast general work and reasoning.', + description: 'Current GPT models for general work, coding, and reasoning.', envKey: 'OPENAI_API_KEY', supportsApiKey: true, supportsBaseUrl: true, @@ -148,11 +148,19 @@ function createDefaultAiSettings() { cost_mode: 'balanced_auto', chat_history_window: 20, tool_replay_budget_chars: 6000, + tool_replay_budget_file_chars: null, + tool_replay_budget_browser_chars: null, + tool_replay_budget_command_chars: null, + max_iterations: null, + max_consecutive_read_only_iterations: null, + max_consecutive_tool_failures: null, + max_model_failure_recoveries: null, + compaction_threshold: null, subagent_max_iterations: 6, subagent_max_children_per_run: 10, assistant_behavior_notes: '', auto_skill_learning: false, - fallback_model_id: 'gpt-5-nano', + fallback_model_id: 'openai::gpt-5-nano', smarter_model_selector: true, enabled_models: [], default_chat_model: 'auto', @@ -179,6 +187,14 @@ function parseSettingValue(value) { } } +function normalizeOptionalNumber(value, min, max, { integer = false } = {}) { + if (value == null || value === '') return null; + const parsed = Number(value); + if (!Number.isFinite(parsed)) return null; + const normalized = integer ? Math.floor(parsed) : parsed; + return Math.min(Math.max(normalized, min), max); +} + function normalizeProviderConfigs(rawConfigs) { const defaults = createDefaultProviderConfigs(); const parsed = rawConfigs && typeof rawConfigs === 'object' && !Array.isArray(rawConfigs) @@ -325,6 +341,14 @@ function getAiSettings(userId, agentId = null) { settings.chat_history_window = Math.max(6, Math.min(Number(settings.chat_history_window) || DEFAULT_AI_SETTINGS.chat_history_window, 40)); settings.tool_replay_budget_chars = Math.max(1200, Math.min(Number(settings.tool_replay_budget_chars) || DEFAULT_AI_SETTINGS.tool_replay_budget_chars, 12000)); + settings.tool_replay_budget_file_chars = normalizeOptionalNumber(settings.tool_replay_budget_file_chars, 500, 500_000, { integer: true }); + settings.tool_replay_budget_browser_chars = normalizeOptionalNumber(settings.tool_replay_budget_browser_chars, 500, 500_000, { integer: true }); + settings.tool_replay_budget_command_chars = normalizeOptionalNumber(settings.tool_replay_budget_command_chars, 500, 500_000, { integer: true }); + settings.max_iterations = normalizeOptionalNumber(settings.max_iterations, 1, 400, { integer: true }); + settings.max_consecutive_read_only_iterations = normalizeOptionalNumber(settings.max_consecutive_read_only_iterations, 3, 25, { integer: true }); + settings.max_consecutive_tool_failures = normalizeOptionalNumber(settings.max_consecutive_tool_failures, 1, 50, { integer: true }); + settings.max_model_failure_recoveries = normalizeOptionalNumber(settings.max_model_failure_recoveries, 0, 10, { integer: true }); + settings.compaction_threshold = normalizeOptionalNumber(settings.compaction_threshold, 0.1, 1); settings.subagent_max_iterations = Math.max(2, Math.min(Number(settings.subagent_max_iterations) || DEFAULT_AI_SETTINGS.subagent_max_iterations, 12)); settings.subagent_max_children_per_run = Math.max( 1, diff --git a/server/services/ai/taskAnalysis.js b/server/services/ai/taskAnalysis.js index e0f1498e..a5002e9b 100644 --- a/server/services/ai/taskAnalysis.js +++ b/server/services/ai/taskAnalysis.js @@ -17,6 +17,7 @@ const TASK_ANALYSIS_CONFIDENCE_DEFAULT = 0.55; const VERIFICATION_CONFIDENCE_VERIFIED = 0.85; const VERIFICATION_CONFIDENCE_DEFAULT = 0.5; const JSON_ONLY_RESPONSE_RULE = 'Return JSON only. No markdown, no prose, no code fences.'; +const { isDeferredWorkReply } = require('./terminal_reply'); const ANALYSIS_SCHEMA_EXAMPLE = { mode: 'execute', needs_verification: true, @@ -256,7 +257,10 @@ function isDirectAnswerEligibleAnalysis(analysis) { planningDepth: analysis.planning_depth, }); - return promotedMode === 'direct_answer' && !analysis.needs_subagents && Boolean(draftReply); + return promotedMode === 'direct_answer' + && !analysis.needs_subagents + && Boolean(draftReply) + && !isDeferredWorkReply(draftReply); } function extractJsonCandidate(text) { diff --git a/server/services/ai/terminal_reply.js b/server/services/ai/terminal_reply.js new file mode 100644 index 00000000..d2edc64e --- /dev/null +++ b/server/services/ai/terminal_reply.js @@ -0,0 +1,45 @@ +'use strict'; + +function normalizeReply(content) { + return String(content || '') + .replace(/[\u2018\u2019]/g, "'") + .replace(/^[\s>*_`#-]+/, '') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase(); +} + +function hasExternalBlocker(text) { + return /\b(?:blocked|cannot|can't|could not|couldn't|unable to|do not have access|don't have access|missing (?:access|credentials|permission|information)|need you to|requires? your|waiting for your|please (?:provide|send|choose|confirm|authorize|approve))\b/.test(text); +} + +/** + * Detect replies that only announce or promise work which has not happened yet. + * This is intentionally conservative and supplements the model completion judge; + * it is a deterministic last line of defence against terminating after a status + * phrase such as "I'm working on it" or "let me check". + */ +function isDeferredWorkReply(content) { + const text = normalizeReply(content); + if (!text || hasExternalBlocker(text)) return false; + + const acknowledgement = '(?:(?:sure|okay|ok|alright|absolutely|of course|got it|understood)[,.!]?\\s+)?'; + const taskTarget = '(?:it|this|that|your\\s+(?:request|task|issue)|the\\s+(?:request|task|issue|problem|code|logs?|repository|repo|build|tests?))'; + const activeWork = `(?:working\\s+(?:on|through)\\s+${taskTarget}|checking(?:\\s+${taskTarget})?|looking\\s+(?:into|at)\\s+${taskTarget}|investigating\\s+${taskTarget}|reviewing\\s+${taskTarget}|researching\\s+${taskTarget}|testing\\s+${taskTarget}|debugging\\s+${taskTarget}|running\\s+${taskTarget}|processing\\s+${taskTarget}|handling\\s+${taskTarget}|starting\\s+${taskTarget}|continuing\\s+${taskTarget})`; + const promisedWork = '(?:check|look\\s+into|investigate|review|research|test|debug|run|work\\s+on|handle|start|continue|fix|send|create|update|delete|install|restart|deploy|publish|do\\s+that|take\\s+care\\s+of)'; + const patterns = [ + new RegExp(`^${acknowledgement}i(?:'m| am)\\s+(?:(?:still|currently|now|already)\\s+)?${activeWork}\\b`), + new RegExp(`^${acknowledgement}i(?:'ll| will)\\s+(?:now\\s+)?${promisedWork}\\b`), + new RegExp(`^${acknowledgement}(?:let me|allow me to)\\s+${promisedWork}\\b`), + new RegExp(`^${acknowledgement}(?:i(?:'m| am)\\s+going to)\\s+${promisedWork}\\b`), + /^(?:(?:please )?give me\s+(?:(?:a|one|another)\s+)?(?:moment|minute|bit)|hang tight|please wait|one moment|bear with me)\b/, + /^(?:working on it|checking now|on it)[.!…]*$/, + /\bi(?:'ll| will)\s+(?:get back to you|update you|report back|let you know|keep you posted)\b/, + /\b(?:i(?:'ll| will)\s+follow up|stay tuned)\b/, + ]; + return patterns.some((pattern) => pattern.test(text)); +} + +module.exports = { + isDeferredWorkReply, +}; diff --git a/server/services/ai/toolEvidence.js b/server/services/ai/toolEvidence.js index 777fbadc..2da3e4bb 100644 --- a/server/services/ai/toolEvidence.js +++ b/server/services/ai/toolEvidence.js @@ -34,30 +34,50 @@ const EVIDENCE_SOURCE_RULES = [ { source: 'subagent', match: (name) => name.includes('subagent') }, ]; +const STATE_CHANGING_DEVICE_TOOLS = new Set([ + 'android_install_apk', + 'android_long_press', + 'android_open_app', + 'android_open_intent', + 'android_press_key', + 'android_start_emulator', + 'android_stop_emulator', + 'android_swipe', + 'android_tap', + 'android_type', + 'desktop_click', + 'desktop_drag', + 'desktop_launch_app', + 'desktop_press_key', + 'desktop_scroll', + 'desktop_select_device', + 'desktop_type', +]); + function deriveEvidenceSource(name) { const rule = EVIDENCE_SOURCE_RULES.find((entry) => entry.match(name)); return rule ? rule.source : 'tool'; } -function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '') { +function resolveDeclaredToolAccess(toolDefinition, toolArgs = {}) { + const access = String(toolDefinition?.access || '').trim().toLowerCase(); + if (access === 'read' || access === 'write') return access; + if (access !== 'dynamic_http_method') return null; + + const method = String(toolArgs?.method || toolArgs?.http_method || 'GET') + .trim() + .toUpperCase(); + return ['GET', 'HEAD', 'OPTIONS'].includes(method) ? 'read' : 'write'; +} + +function classifyToolExecution( + toolName, + toolArgs = {}, + result, + errorMessage = '', + toolDefinition = null, +) { const name = String(toolName || ''); - const evidenceRelevantPrefixes = ['browser_', 'android_']; - const evidenceRelevantExact = new Set([ - 'web_search', - 'http_request', - 'read_file', - 'read_files', - 'search_files', - 'list_directory', - 'code_navigate', - 'query_structured_data', - 'session_search', - 'memory_recall', - 'analyze_image', - 'read_health_data', - 'list_tasks', - 'wait_subagent', - ]); const stateChangingExact = new Set([ 'execute_command', 'write_file', @@ -79,22 +99,30 @@ function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = ' 'mcp_add_server', 'mcp_remove_server', 'spawn_subagent', + 'delegate_to_agent', 'cancel_subagent', ]); - const evidenceSource = deriveEvidenceSource(name); + const declaredAccess = resolveDeclaredToolAccess(toolDefinition, toolArgs); + const evidenceSource = declaredAccess ? 'integration' : deriveEvidenceSource(name); - const evidenceRelevant = evidenceRelevantExact.has(name) - || evidenceRelevantPrefixes.some((prefix) => name.startsWith(prefix)); - const stateChanged = ( - name === 'execute_command' - ? !isClearlyReadOnlyShellCommand(toolArgs?.command || '') - : stateChangingExact.has(name) - ) + // Any successful, substantive tool result can advance the run. This default + // is deliberate: MCP, skills, and newly added integrations must not become + // invisible to the churn guard just because their names were not added to a + // central allow-list. Repetition detection still rejects unchanged retries. + const evidenceRelevant = isSubstantiveProgressToolName(name); + let directStateChange; + if (name === 'execute_command' || name === 'android_shell') { + directStateChange = !isClearlyReadOnlyShellCommand(toolArgs?.command || ''); + } else { + directStateChange = declaredAccess === 'write' + || STATE_CHANGING_DEVICE_TOOLS.has(name) + || stateChangingExact.has(name); + } + const stateChanged = directStateChange || (name.startsWith('github_') && isProgressToolCall(name, toolArgs)) || (name === 'http_request' && isProgressToolCall(name, toolArgs)) - || name.startsWith('android_') - || ['browser_click', 'browser_type', 'browser_evaluate'].includes(name); + || ['browser_click', 'browser_evaluate', 'browser_navigate', 'browser_type'].includes(name); let normalizedError = String(errorMessage || result?.error || '').trim(); if (!normalizedError && name === 'execute_command' && result && typeof result === 'object') { @@ -167,7 +195,7 @@ function gatheredNewEvidence(execution, repetitionObservation = null) { function isSubstantiveProgressToolName(toolName = '') { const name = String(toolName || '').trim(); if (!name) return false; - if (name === 'send_message' || name === 'send_interim_update' || name === 'make_call') return false; + if (name === 'send_message' || name === 'send_interim_update' || name === 'make_call' || name === 'notify_user') return false; if (name === 'think' || name === 'activate_tools' || name === 'task_complete') return false; return true; } @@ -254,4 +282,5 @@ module.exports = { summarizeAvailableTools, inferToolFailureMessage, buildAutonomousRecoveryContext, + resolveDeclaredToolAccess, }; diff --git a/server/services/ai/tools.js b/server/services/ai/tools.js index 39d9ad39..47c5dac6 100644 --- a/server/services/ai/tools.js +++ b/server/services/ai/tools.js @@ -1,7 +1,9 @@ const fs = require('fs'); const path = require('path'); const { analyzeImageForUser } = require('./imageAnalysis'); -const { isPrivateHost, validateCloudUrl } = require('../../utils/cloud-security'); +const { validateCloudUrlWithDns } = require('../../utils/cloud-security'); +const { isAbortError } = require('../../utils/abort'); +const { fetchResponseText } = require('../network/http'); const db = require('../../db/database'); const { DATA_DIR } = require('../../../runtime/paths'); const { isMainAgent } = require('../agents/manager'); @@ -10,11 +12,13 @@ const { normalizeOutgoingMessageForPlatform, } = require('../messaging/formatting_guides'); const { INTERIM_KINDS, normalizeInterimKind } = require('./interim'); +const { isDeferredWorkReply } = require('./terminal_reply'); const { normalizeWhatsAppId } = require('../../utils/whatsapp'); const { executeIntegratedTool, getIntegratedToolDefinitions, } = require('./integrated_tools'); +const { executeHttpRequest } = require('./integrated_tools/http_request'); function compactText(text, maxChars = 120) { const str = String(text || '').replace(/\s+/g, ' ').trim(); @@ -34,6 +38,14 @@ function compactToolDefinition(tool, options = {}) { } }; + // Keep execution-only access metadata on the internal tool definition. The + // provider adapters serialize only name/description/parameters, so this is + // never sent as part of an LLM tool schema. The loop uses it to distinguish + // official-integration reads from writes without hard-coding provider names. + if (typeof tool.access === 'string' && tool.access.trim()) { + compact.access = tool.access.trim().toLowerCase(); + } + if (options.includeDescriptions) { compact.description = compactText(tool.description, 320); } @@ -878,7 +890,7 @@ function getAvailableTools(app, options = {}) { }, { name: 'send_message', - description: `Send a message on a connected messaging platform. Supports WhatsApp (text/media), Telnyx Voice (phone calls — TTS), Discord, Telegram, Slack, Google Chat, Microsoft Teams, Matrix, Signal, iMessage/BlueBubbles, IRC, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, WeChat, WebChat, and configurable webhook bridges. ${buildSendMessageFormattingReference()} For WhatsApp: use media_path to attach files. Use content "[NO RESPONSE]" only when the user explicitly asked for silence/no reply, or when a background task intentionally decides no user-visible update is needed with purpose="no_response". For background task or schedule runs, set purpose to final_result, blocker, or no_response.`, + description: `Send a final message on a connected messaging platform. Use send_interim_update, not this tool, for an ongoing status reply to the originating chat. Supports WhatsApp (text/media), Telnyx Voice (phone calls — TTS), Discord, Telegram, Slack, Google Chat, Microsoft Teams, Matrix, Signal, iMessage/BlueBubbles, IRC, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, WeChat, WebChat, and configurable webhook bridges. ${buildSendMessageFormattingReference()} For WhatsApp: use media_path to attach files. Use content "[NO RESPONSE]" only when the user explicitly asked for silence/no reply, or when a background task intentionally decides no user-visible update is needed with purpose="no_response". For background task or schedule runs, set purpose to final_result, blocker, or no_response.`, parameters: { type: 'object', properties: { @@ -1632,7 +1644,8 @@ async function executeTool(toolName, args, context, engine) { taskId, widgetId, deliveryState = null, - allowMultipleProactiveMessages = false + allowMultipleProactiveMessages = false, + signal = null, } = context; const runtime = () => app?.locals?.runtimeManager || engine.runtimeManager || null; const bc = async () => { @@ -1641,7 +1654,10 @@ async function executeTool(toolName, args, context, engine) { const backend = typeof manager.getActiveBrowserBackend === 'function' ? await Promise.resolve(manager.getActiveBrowserBackend(userId)) : 'vm'; - return { provider: await manager.getBrowserProviderForUser(userId), backend }; + return { + provider: await manager.getBrowserProviderForUser(userId, { signal }), + backend, + }; } throw new Error('Browser provider is unavailable. VM runtime is required.'); }; @@ -1672,7 +1688,13 @@ async function executeTool(toolName, args, context, engine) { const integrationManager = integrations(); if (integrationManager) { - const integrationResult = await integrationManager.executeTool(userId, toolName, args, agentId); + const integrationResult = await integrationManager.executeTool( + userId, + toolName, + args, + agentId, + { signal }, + ); if ( integrationResult && typeof integrationResult === 'object' && @@ -1724,6 +1746,7 @@ async function executeTool(toolName, args, context, engine) { stdinInput: args.stdin_input, pty: args.pty === true, inputs: Array.isArray(args.inputs) ? args.inputs : [], + signal, }; if (typeof runtimeManager.executeCliCommand === 'function') { return await runtimeManager.executeCliCommand(userId, args.command, execOptions); @@ -1736,7 +1759,7 @@ async function executeTool(toolName, args, context, engine) { } case 'browser_navigate': { - const urlCheck = validateCloudUrl(args.url); + const urlCheck = await validateCloudUrlWithDns(args.url, { signal }); if (!urlCheck.allowed) return { error: 'URL is not allowed: blocked scheme or private/internal network address.' }; const { provider, backend } = await bc(); if (!provider) return { error: 'Browser controller not available' }; @@ -1745,14 +1768,15 @@ async function executeTool(toolName, args, context, engine) { waitFor: args.waitFor, fullPage: args.fullPage, referrerMode: args.referrerMode, - challengeRetry: args.challengeRetry + challengeRetry: args.challengeRetry, + signal, }), backend }; } case 'browser_click': { const { provider, backend } = await bc(); if (!provider) return { error: 'Browser controller not available' }; - return { ...await provider.click(args.selector, args.text, args.screenshot !== false), backend }; + return { ...await provider.click(args.selector, args.text, args.screenshot !== false, { signal }), backend }; } case 'browser_type': { @@ -1760,20 +1784,21 @@ async function executeTool(toolName, args, context, engine) { if (!provider) return { error: 'Browser controller not available' }; return { ...await provider.type(args.selector, args.text, { clear: args.clear !== false, - pressEnter: args.pressEnter + pressEnter: args.pressEnter, + signal, }), backend }; } case 'browser_extract': { const { provider, backend } = await bc(); if (!provider) return { error: 'Browser controller not available' }; - return { ...await provider.extract(args.selector, args.attribute, args.all), backend }; + return { ...await provider.extract(args.selector, args.attribute, args.all, { signal }), backend }; } case 'browser_screenshot': { const { provider, backend } = await bc(); if (!provider) return { error: 'Browser controller not available' }; - return { ...await provider.screenshot({ fullPage: args.fullPage, selector: args.selector }), backend }; + return { ...await provider.screenshot({ fullPage: args.fullPage, selector: args.selector, signal }), backend }; } case 'browser_evaluate': { @@ -1781,13 +1806,13 @@ async function executeTool(toolName, args, context, engine) { if (!provider) return { error: 'Browser controller not available' }; const script = args.script ?? args.javascript; if (!script) return { error: 'browser_evaluate requires a "script" argument' }; - return { ...await provider.evaluate(script), backend }; + return { ...await provider.evaluate(script, { signal }), backend }; } case 'android_start_emulator': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.startEmulator(args || {}); + return await controller.startEmulator({ ...(args || {}), signal }); } case 'desktop_list_devices': { @@ -1814,6 +1839,7 @@ async function executeTool(toolName, args, context, engine) { return await controller.observe({ deviceId: args.device_id, includeTree: args.includeTree === true, + signal, }); } @@ -1823,6 +1849,7 @@ async function executeTool(toolName, args, context, engine) { return await controller.clickPoint(args.x, args.y, { deviceId: args.device_id, button: args.button, + signal, }); } @@ -1836,6 +1863,7 @@ async function executeTool(toolName, args, context, engine) { x2: args.x2, y2: args.y2, durationMs: args.durationMs, + signal, }); } @@ -1846,6 +1874,7 @@ async function executeTool(toolName, args, context, engine) { deviceId: args.device_id, deltaX: args.deltaX, deltaY: args.deltaY, + signal, }); } @@ -1855,6 +1884,7 @@ async function executeTool(toolName, args, context, engine) { return await controller.typeText(args.text, { deviceId: args.device_id, pressEnter: args.pressEnter === true, + signal, }); } @@ -1863,6 +1893,7 @@ async function executeTool(toolName, args, context, engine) { if (!controller) return { error: 'Desktop provider not available' }; return await controller.pressKey(args.key, { deviceId: args.device_id, + signal, }); } @@ -1872,6 +1903,7 @@ async function executeTool(toolName, args, context, engine) { return await controller.launchApp({ deviceId: args.device_id, app: args.app, + signal, }); } @@ -1880,112 +1912,111 @@ async function executeTool(toolName, args, context, engine) { if (!controller) return { error: 'Desktop provider not available' }; return await controller.getAccessibilityTree({ deviceId: args.device_id, + signal, }); } case 'android_stop_emulator': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.stopEmulator(); + return await controller.stopEmulator({ signal }); } case 'android_list_devices': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return { devices: await controller.listDevices() }; + return { devices: await controller.listDevices({ signal }) }; } case 'android_open_app': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.openApp(args || {}); + return await controller.openApp({ ...(args || {}), signal }); } case 'android_open_intent': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.openIntent(args || {}); + return await controller.openIntent({ ...(args || {}), signal }); } case 'android_tap': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.tap(args || {}); + return await controller.tap({ ...(args || {}), signal }); } case 'android_long_press': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.longPress(args || {}); + return await controller.longPress({ ...(args || {}), signal }); } case 'android_type': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.type(args || {}); + return await controller.type({ ...(args || {}), signal }); } case 'android_swipe': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.swipe(args || {}); + return await controller.swipe({ ...(args || {}), signal }); } case 'android_press_key': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.pressKey(args || {}); + return await controller.pressKey({ ...(args || {}), signal }); } case 'android_wait_for': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.waitFor(args || {}); + return await controller.waitFor({ ...(args || {}), signal }); } case 'android_observe': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.observe(args || {}); + return await controller.observe({ ...(args || {}), signal }); } case 'android_dump_ui': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.dumpUi(args || {}); + return await controller.dumpUi({ ...(args || {}), signal }); } case 'android_screenshot': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.screenshot(args || {}); + return await controller.screenshot({ ...(args || {}), signal }); } case 'android_list_apps': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.listApps(args || {}); + return await controller.listApps({ ...(args || {}), signal }); } case 'android_install_apk': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.installApk(args || {}); + return await controller.installApk({ ...(args || {}), signal }); } case 'android_shell': { const controller = await ac(); if (!controller) return { error: 'Android controller not available' }; - return await controller.shell(args || {}); + return await controller.shell({ ...(args || {}), signal }); } case 'web_search': { const apiKey = process.env.BRAVE_SEARCH_API_KEY; if (!apiKey) return { error: 'BRAVE_SEARCH_API_KEY is not configured' }; - const controller = new AbortController(); const timeoutMs = 20000; - const timer = setTimeout(() => controller.abort(), timeoutMs); try { const limit = Math.max(1, Math.min(Number(args.count) || 5, 10)); @@ -2000,15 +2031,20 @@ async function executeTool(toolName, args, context, engine) { if (args.search_lang) params.set('search_lang', String(args.search_lang).toLowerCase()); if (args.freshness) params.set('freshness', args.freshness); - const res = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, { + const { response, text } = await fetchResponseText( + `https://api.search.brave.com/res/v1/web/search?${params.toString()}`, + { headers: { Accept: 'application/json', 'X-Subscription-Token': apiKey }, - signal: controller.signal - }); + maxResponseBytes: 2 * 1024 * 1024, + serviceName: 'Brave Search', + signal, + timeoutMs, + }, + ); - const text = await res.text(); let data = null; try { data = JSON.parse(text); @@ -2016,9 +2052,9 @@ async function executeTool(toolName, args, context, engine) { data = null; } - if (!res.ok) { + if (!response.ok) { return { - error: `Brave Search API request failed with status ${res.status}`, + error: `Brave Search API request failed with status ${response.status}`, details: data || text.slice(0, 1000) }; } @@ -2040,10 +2076,11 @@ async function executeTool(toolName, args, context, engine) { results }; } catch (err) { - if (err.name === 'AbortError') return { error: `Brave Search API request timed out after ${timeoutMs} ms` }; + if (isAbortError(err, signal)) throw err; + if (err.code === 'HTTP_TIMEOUT') { + return { error: `Brave Search API request timed out after ${timeoutMs}ms` }; + } return { error: err.message }; - } finally { - clearTimeout(timer); } } @@ -2051,7 +2088,13 @@ async function executeTool(toolName, args, context, engine) { const { MemoryManager } = require('../memory/manager'); const mm = new MemoryManager(); const content = typeof args.content === 'string' ? args.content : args.value; - const id = await mm.saveMemory(userId, content, args.category || 'episodic', args.importance || 5, { agentId }); + const id = await mm.saveMemory( + userId, + content, + args.category || 'episodic', + args.importance || 5, + { agentId, signal }, + ); if (!id) { return { success: true, @@ -2065,7 +2108,12 @@ async function executeTool(toolName, args, context, engine) { case 'memory_recall': { const { MemoryManager } = require('../memory/manager'); const mm = new MemoryManager(); - const results = await mm.recallMemory(userId, args.query, args.limit || 6, { agentId }); + const results = await mm.recallMemory( + userId, + args.query, + args.limit || 6, + { agentId, signal }, + ); if (!results.length) return { results: [], message: 'Nothing found' }; return { results }; } @@ -2198,6 +2246,7 @@ async function executeTool(toolName, args, context, engine) { includeFrame: args.include_frame !== false, forceStt: args.force_stt === true, agentId, + signal, }); } @@ -2206,7 +2255,7 @@ async function executeTool(toolName, args, context, engine) { if (!service || typeof service.getStatus !== 'function') { return { error: 'Social reach service is unavailable.' }; } - return await service.getStatus(userId); + return await service.getStatus(userId, { signal }); } case 'social_reach_read': { @@ -2214,7 +2263,7 @@ async function executeTool(toolName, args, context, engine) { if (!service || typeof service.read !== 'function') { return { error: 'Social reach service is unavailable.' }; } - return await service.read(userId, args || {}); + return await service.read(userId, args || {}, { signal }); } case 'social_reach_search': { @@ -2222,7 +2271,7 @@ async function executeTool(toolName, args, context, engine) { if (!service || typeof service.search !== 'function') { return { error: 'Social reach service is unavailable.' }; } - return await service.search(userId, args || {}); + return await service.search(userId, args || {}, { signal }); } case 'memory_write': { @@ -2305,6 +2354,23 @@ async function executeTool(toolName, args, context, engine) { stripNoResponseMarker: false }); const suppressReply = normalizedMessage === '[NO RESPONSE]'; + const originDelivery = isOriginMessagingDelivery({ + triggerSource, + source: context.source, + chatId: context.chatId, + platform: args.platform, + to: args.to, + }); + if ( + !suppressReply + && triggerSource === 'messaging' + && originDelivery + && isDeferredWorkReply(normalizedMessage) + ) { + return { + error: 'send_message cannot end the run with a promise or progress-only reply. Continue the work, or use send_interim_update for a factual interim update.', + }; + } if (isProactiveTrigger(triggerSource)) { const proactiveValidation = validateProactiveSendMessageArgs({ purpose: args.purpose, @@ -2360,24 +2426,22 @@ async function executeTool(toolName, args, context, engine) { }; } + if (triggerSource === 'messaging' && originDelivery) { + await engine?.stopMessagingProgressSupervisor?.(runId); + } const sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, { agentId, mediaPath: args.media_path, runId, - persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks' + persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks', + signal, }); // Track that the agent explicitly sent a message during this run if ( !suppressReply && sendResult?.success === true && sendResult?.suppressed !== true - && isOriginMessagingDelivery({ - triggerSource, - source: context.source, - chatId: context.chatId, - platform: args.platform, - to: args.to, - }) + && originDelivery ) { markProactiveMessageSent({ runState, deliveryState, content: normalizedMessage }); if (runState && triggerSource === 'messaging') { @@ -2557,66 +2621,14 @@ async function executeTool(toolName, args, context, engine) { } case 'http_request': { - let parsedUrl; - try { parsedUrl = new URL(args.url); } catch { - return { error: 'Invalid URL' }; - } - const scheme = parsedUrl.protocol.replace(/:$/, '').toLowerCase(); - if (!['http', 'https'].includes(scheme)) { - return { error: 'URL scheme not allowed. Only http and https are permitted.' }; - } - const h = parsedUrl.hostname.toLowerCase().replace(/^\[|\]$/g, ''); - if (h === 'localhost' || h === '127.0.0.1' || h === '::1' || h.endsWith('.localhost')) { - return { error: 'Loopback addresses are not permitted.' }; - } - const allowPrivate = process.env.NEOAGENT_HTTP_ALLOW_PRIVATE !== 'false'; - if (!allowPrivate && isPrivateHost(parsedUrl.hostname)) { - return { error: 'Private/internal network addresses are not permitted.' }; - } - const controller = new AbortController(); - const timeoutMs = args.timeout_ms || 30000; - const timer = setTimeout(() => controller.abort(), timeoutMs); try { - const options = { - method: args.method || 'GET', - headers: args.headers || {}, - signal: controller.signal - }; - if (args.body && ['POST', 'PUT', 'PATCH'].includes(options.method)) { - options.body = args.body; - if (!options.headers['Content-Type']) { - options.headers['Content-Type'] = 'application/json'; - } - } - const res = await fetch(args.url, options); - const MAX_BODY = 512 * 1024; - const reader = res.body.getReader(); - const chunks = []; - let total = 0; - let truncated = false; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - total += value.length; - if (total > MAX_BODY) { - const take = MAX_BODY - (total - value.length); - if (take > 0) chunks.push(value.slice(0, take)); - truncated = true; - break; - } - chunks.push(value); - } - const text = Buffer.concat(chunks.map(c => Buffer.from(c))).toString('utf-8'); - return { - status: res.status, - headers: Object.fromEntries(res.headers.entries()), - body: truncated ? text + '\n...[truncated]' : text, - }; + return await executeHttpRequest(args, { + allowPrivate: process.env.NEOAGENT_HTTP_ALLOW_PRIVATE === 'true', + signal, + }); } catch (err) { - if (err.name === 'AbortError') return { error: `Request timed out after ${timeoutMs} ms` }; + if (isAbortError(err, signal)) throw err; return { error: err.message }; - } finally { - clearTimeout(timer); } } @@ -2762,7 +2774,8 @@ async function executeTool(toolName, args, context, engine) { const sendResult = await manager.sendMessage(userId, target.platform, target.to, message, { agentId, runId, - persistConversation: true + persistConversation: true, + signal, }); if (taskId && taskConfig && (taskConfig.notifyPlatform !== target.platform || taskConfig.notifyTo !== target.to)) { taskConfig.notifyPlatform = target.platform; diff --git a/server/services/android/controller.js b/server/services/android/controller.js index 75294852..6f846543 100644 --- a/server/services/android/controller.js +++ b/server/services/android/controller.js @@ -1,29 +1,34 @@ 'use strict'; -const { spawn, spawnSync } = require('child_process'); +const { spawn } = require('child_process'); const fs = require('fs'); -const https = require('https'); const net = require('net'); const os = require('os'); const path = require('path'); -const { DATA_DIR } = require('../../../runtime/paths'); +const { DATA_DIR, RUNTIME_HOME } = require('../../../runtime/paths'); +const { validateAndroidIntentUrl } = require('../../utils/cloud-security'); +const { validateImageBuffer } = require('../../utils/image_payload'); +const { downloadFile, resolveCommandLineToolsRelease } = require('./sdk_download'); +const { findBestNode, parseUiDump, summarizeNode } = require('./uia'); +const { clampNumber, runProcess } = require('./process'); // ─── Constants ─────────────────────────────────────────────────────────────── -const DEFAULT_SDK_DIR = path.join(os.homedir(), '.neoagent', 'android-sdk'); +const DEFAULT_SDK_DIR = path.join(RUNTIME_HOME, 'android-sdk'); +const DEFAULT_AVD_DIR = path.join(RUNTIME_HOME, 'android', 'avd'); const STATE_DIR = path.join(DATA_DIR, 'android', 'state'); const LOGO_PATH = path.join(__dirname, '..', '..', '..', 'flutter_app', 'assets', 'branding', 'app_icon_512.png'); -// Even ports in 5554–5682 (documented ADB range). 65 slots for 65 concurrent users. +// Even console ports in the documented emulator range. Each emulator also owns +// the adjacent ADB port, so 64 pairs fit without crossing the supported range. const ADB_PORT_BASE = 5554; -const ADB_PORT_SLOTS = 65; +const ADB_PORT_SLOTS = 64; +const RESERVED_ADB_PORTS = new Set(); +const SDK_PROVISIONING = new Map(); -const CMDLINE_TOOLS_VERSION = '14742923'; -const CMDLINE_TOOLS_URLS = { - darwin: `https://dl.google.com/android/repository/commandlinetools-mac-${CMDLINE_TOOLS_VERSION}_latest.zip`, - linux: `https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip`, - win32: `https://dl.google.com/android/repository/commandlinetools-win-${CMDLINE_TOOLS_VERSION}_latest.zip`, -}; +const MAX_ANDROID_TEXT_CHARS = 8000; +const MAX_ANDROID_INTENT_EXTRAS = 100; +const MAX_ANDROID_PACKAGE_BYTES = 1024 * 1024 * 1024; fs.mkdirSync(STATE_DIR, { recursive: true }); @@ -38,7 +43,14 @@ function readState(userId) { function writeState(userId, patch) { const current = readState(userId); - fs.writeFileSync(stateFile(userId), JSON.stringify({ ...current, ...patch }, null, 2)); + const destination = stateFile(userId); + const temporary = `${destination}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`; + try { + fs.writeFileSync(temporary, JSON.stringify({ ...current, ...patch }, null, 2), { mode: 0o600 }); + fs.renameSync(temporary, destination); + } finally { + try { fs.unlinkSync(temporary); } catch {} + } } // ─── SDK resolution ────────────────────────────────────────────────────────── @@ -111,86 +123,197 @@ function pickSystemImage(sdkDir) { function defaultSystemImage() { const abi = (os.arch() === 'arm64' || os.arch() === 'arm') ? 'arm64-v8a' : 'x86_64'; - return `system-images;android-33;google_apis;${abi}`; + return `system-images;android-36;google_apis;${abi}`; } // ─── SDK setup ─────────────────────────────────────────────────────────────── -function downloadFile(url, dest) { +function abortError(signal, message = 'Android operation was aborted.') { + if (signal?.reason instanceof Error) return signal.reason; + const error = new Error(String(signal?.reason || message)); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +function delay(ms, signal = null) { + if (signal?.aborted) return Promise.reject(abortError(signal)); return new Promise((resolve, reject) => { - const file = fs.createWriteStream(dest); - const follow = u => https.get(u, res => { - if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - file.close(); return follow(res.headers.location); - } - if (res.statusCode !== 200) { file.close(); return reject(new Error(`HTTP ${res.statusCode}`)); } - res.pipe(file); - file.on('finish', () => file.close(resolve)); - }).on('error', err => { file.close(); fs.unlink(dest, () => {}); reject(err); }); - follow(url); + const timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject(abortError(signal)); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + if (signal?.aborted) onAbort(); + }); +} + +function waitForAbortable(promise, signal = null) { + if (signal?.aborted) return Promise.reject(abortError(signal)); + if (!signal) return promise; + return new Promise((resolve, reject) => { + let settled = false; + const finish = (callback, value) => { + if (settled) return; + settled = true; + signal.removeEventListener('abort', onAbort); + callback(value); + }; + const onAbort = () => finish(reject, abortError(signal)); + signal.addEventListener('abort', onAbort, { once: true }); + if (signal.aborted) onAbort(); + Promise.resolve(promise).then( + (value) => finish(resolve, value), + (error) => finish(reject, error), + ); }); } -async function ensureSdk(sdkDir, onProgress) { +async function ensureSdk(sdkDir, onProgress, options = {}) { if (fs.existsSync(sdkManagerBin(sdkDir))) return; - const url = CMDLINE_TOOLS_URLS[process.platform]; - if (!url) throw new Error(`No cmdline-tools download for platform: ${process.platform}`); + const release = resolveCommandLineToolsRelease(); fs.mkdirSync(sdkDir, { recursive: true }); - onProgress('Downloading Android SDK command-line tools (~150 MB)…'); - const zip = path.join(os.tmpdir(), 'cmdline-tools.zip'); - await downloadFile(url, zip); + onProgress('Downloading Android SDK command-line tools (~182 MB)…'); + const downloadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'neoagent-android-sdk-')); + const zip = path.join(downloadDir, 'cmdline-tools.zip'); + try { + await downloadFile(release.url, zip, { + expectedSha256: release.sha256, + signal: options.signal, + }); + await runProcess('unzip', ['-tq', zip], { + timeoutMs: 120_000, + maxOutputBytes: 1024 * 1024, + signal: options.signal, + }); - onProgress('Extracting…'); - const toolsDir = path.join(sdkDir, 'cmdline-tools'); - fs.mkdirSync(toolsDir, { recursive: true }); - const unzip = spawnSync('unzip', ['-qo', zip, '-d', toolsDir]); - fs.unlinkSync(zip); - if (unzip.status !== 0) throw new Error('unzip failed'); + onProgress('Extracting…'); + const toolsDir = path.join(sdkDir, 'cmdline-tools'); + fs.mkdirSync(toolsDir, { recursive: true }); + await runProcess('unzip', ['-qo', zip, '-d', toolsDir], { + timeoutMs: 120_000, + maxOutputBytes: 1024 * 1024, + signal: options.signal, + }); + } finally { + fs.rmSync(downloadDir, { recursive: true, force: true }); + } + const toolsDir = path.join(sdkDir, 'cmdline-tools'); const extracted = path.join(toolsDir, 'cmdline-tools'); const latest = path.join(toolsDir, 'latest'); if (fs.existsSync(extracted) && !fs.existsSync(latest)) fs.renameSync(extracted, latest); if (!fs.existsSync(sdkManagerBin(sdkDir))) throw new Error('sdkmanager not found after extraction'); } -async function ensurePackages(sdkDir, onProgress) { +async function ensurePackages(sdkDir, onProgress, options = {}) { const env = { ...process.env, ANDROID_SDK_ROOT: sdkDir, ANDROID_HOME: sdkDir }; const sdkman = sdkManagerBin(sdkDir); onProgress('Accepting Android SDK licenses…'); - spawnSync(sdkman, ['--licenses', `--sdk_root=${sdkDir}`], { input: 'y\n'.repeat(20), encoding: 'utf8', env, stdio: ['pipe', 'pipe', 'pipe'] }); + await runProcess(sdkman, ['--licenses', `--sdk_root=${sdkDir}`], { + input: 'y\n'.repeat(20), + env, + timeoutMs: 5 * 60 * 1000, + maxOutputBytes: 8 * 1024 * 1024, + signal: options.signal, + }); - const img = defaultSystemImage(); - onProgress(`Installing platform-tools, emulator, ${img} (~1–2 GB, first run only)…`); - const r = spawnSync(sdkman, ['platform-tools', 'emulator', img, `--sdk_root=${sdkDir}`], { - encoding: 'utf8', env, stdio: ['pipe', 'pipe', 'pipe'], timeout: 20 * 60 * 1000, + const existingImage = pickSystemImage(sdkDir); + const packages = ['platform-tools', 'emulator']; + if (!existingImage) packages.push(defaultSystemImage()); + onProgress(`Installing ${packages.join(', ')} (first run only)…`); + await runProcess(sdkman, [...packages, `--sdk_root=${sdkDir}`], { + env, + timeoutMs: 20 * 60 * 1000, + maxOutputBytes: 32 * 1024 * 1024, + signal: options.signal, }); - if (r.status !== 0) throw new Error(`sdkmanager failed: ${(r.stderr || r.stdout || '').slice(0, 500)}`); } -function ensureEmulatorRegistered(sdkDir) { +async function ensureSdkProvisioned(sdkDir, onProgress, options = {}) { + const key = path.resolve(sdkDir); + let entry = SDK_PROVISIONING.get(key); + if (entry?.controller.signal.aborted && !entry.settled) { + await entry.promise.catch(() => {}); + entry = null; + } + if (!entry) { + const controller = new AbortController(); + entry = { + controller, + promise: null, + settled: false, + waiters: 0, + }; + entry.promise = (async () => { + await ensureSdk(sdkDir, onProgress, { signal: controller.signal }); + const hasAdb = fs.existsSync(adbBin(sdkDir)); + const hasEmulator = fs.existsSync(emulatorBin(sdkDir)); + const hasImage = Boolean(pickSystemImage(sdkDir)); + if (!hasAdb || !hasEmulator || !hasImage) { + await ensurePackages(sdkDir, onProgress, { signal: controller.signal }); + } + })(); + SDK_PROVISIONING.set(key, entry); + const settle = () => { + entry.settled = true; + if (SDK_PROVISIONING.get(key) === entry) SDK_PROVISIONING.delete(key); + }; + entry.promise.then(settle, settle); + } + + entry.waiters += 1; + try { + await waitForAbortable(entry.promise, options.signal); + } finally { + entry.waiters -= 1; + if (entry.waiters === 0 && !entry.settled && !entry.controller.signal.aborted) { + entry.controller.abort(new Error('Android SDK provisioning no longer has an active startup request.')); + } + } +} + +async function ensureEmulatorRegistered(sdkDir, options = {}) { const packageXml = path.join(sdkDir, 'emulator', 'package.xml'); if (fs.existsSync(packageXml) || !fs.existsSync(emulatorBin(sdkDir))) return; const env = { ...process.env, ANDROID_SDK_ROOT: sdkDir, ANDROID_HOME: sdkDir }; - spawnSync(sdkManagerBin(sdkDir), ['emulator', `--sdk_root=${sdkDir}`], { - encoding: 'utf8', env, input: 'y\n'.repeat(5), stdio: ['pipe', 'pipe', 'pipe'], timeout: 5 * 60 * 1000, + await runProcess(sdkManagerBin(sdkDir), ['emulator', `--sdk_root=${sdkDir}`], { + env, + input: 'y\n'.repeat(5), + timeoutMs: 5 * 60 * 1000, + maxOutputBytes: 16 * 1024 * 1024, + signal: options.signal, }); } -function ensureAvd(sdkDir, avdName, onProgress) { - const avdDir = path.join(os.homedir(), '.android', 'avd', `${avdName}.avd`); +async function ensureAvd(sdkDir, avdName, avdHome, onProgress, options = {}) { + const avdDir = path.join(avdHome, `${avdName}.avd`); if (fs.existsSync(avdDir)) return; - ensureEmulatorRegistered(sdkDir); + await ensureEmulatorRegistered(sdkDir, options); const img = pickSystemImage(sdkDir) || defaultSystemImage(); onProgress(`Creating AVD "${avdName}" using ${img}…`); - const env = { ...process.env, ANDROID_SDK_ROOT: sdkDir, ANDROID_HOME: sdkDir }; - const r = spawnSync(avdManagerBin(sdkDir), ['create', 'avd', '-n', avdName, '-k', img, '--device', 'pixel', '--force'], { - encoding: 'utf8', env, stdio: ['pipe', 'pipe', 'pipe'], input: '\n', + fs.mkdirSync(avdHome, { recursive: true }); + const env = { + ...process.env, + ANDROID_AVD_HOME: avdHome, + ANDROID_SDK_ROOT: sdkDir, + ANDROID_HOME: sdkDir, + }; + await runProcess(avdManagerBin(sdkDir), ['create', 'avd', '-n', avdName, '-k', img, '--device', 'pixel', '--force'], { + env, + input: '\n', + timeoutMs: 120_000, + maxOutputBytes: 4 * 1024 * 1024, + signal: options.signal, }); - if (r.status !== 0) throw new Error(`avdmanager failed: ${(r.stderr || r.stdout || '').slice(0, 500)}`); // Patch config: sparse QCOW2 (no pre-allocation), smaller cache partition. const cfgPath = path.join(avdDir, 'config.ini'); @@ -222,25 +345,62 @@ function isSafeIdentifier(str) { return /^[\w.]+$/.test(String(str || '')); } +function isSafeActivity(str) { + return /^[\w.$]+$/.test(String(str || '')); +} + +function isSafeComponent(str) { + return /^[\w.$]+\/[\w.$]+$/.test(String(str || '')); +} + +function isSafeMimeType(str) { + return /^[\w.+-]+\/[\w.+*-]+$/.test(String(str || '')); +} + +function normalizeUserId(value) { + const userId = String(value || 'default').trim(); + if (!/^[a-zA-Z0-9_-]{1,64}$/.test(userId)) { + throw new Error('Android user ID contains unsupported characters.'); + } + return userId; +} + +function hasUiSelector(value = {}) { + return ['text', 'resourceId', 'description', 'className', 'packageName'] + .some((key) => String(value[key] || '').trim()) + || value.clickable === true; +} + +function requireCoordinate(value, label) { + const number = Number(value); + if (!Number.isFinite(number) || number < 0) { + throw new Error(`${label} must be a non-negative number.`); + } + return Math.round(number); +} + // ─── AndroidController ─────────────────────────────────────────────────────── class AndroidController { constructor(options = {}) { - this.userId = String(options.userId || 'default').trim(); + this.userId = normalizeUserId(options.userId); this.avdName = `neoagent_${this.userId}`; // Deterministic ADB console port per user, within documented range 5554–5682 (even only). this.adbPort = ADB_PORT_BASE + ((hashCode(this.userId) >>> 0) % ADB_PORT_SLOTS) * 2; this.adbSerial = `emulator-${this.adbPort}`; this.sdkDir = options.sdkDir || findExistingSdk() || DEFAULT_SDK_DIR; + this.avdHome = options.avdHome || DEFAULT_AVD_DIR; this.artifactStore = options.artifactStore || null; this.startPromise = null; + this.startAbortController = null; + this.emulatorProcess = null; } // ── Status ──────────────────────────────────────────────────────────────── getStatusSync() { return readState(this.userId); } - async getStatus() { + async getStatus(options = {}) { const state = readState(this.userId); const base = { bootstrapped: state.bootstrapped || false, @@ -255,204 +415,476 @@ class AndroidController { if (!this.#isPidAlive(state.pid)) return { ...base, bootstrapped: false }; try { - const r = spawnSync(adbBin(this.sdkDir), ['-s', state.adbSerial, 'shell', 'getprop', 'sys.boot_completed'], - { encoding: 'utf8', timeout: 5000 }); - const booted = r.stdout?.trim() === '1'; + const result = await runProcess( + adbBin(this.sdkDir), + ['-s', state.adbSerial, 'shell', 'getprop', 'sys.boot_completed'], + { timeoutMs: 5000, maxOutputBytes: 64 * 1024, signal: options.signal }, + ); + const booted = result.stdout.trim() === '1'; return { ...base, bootstrapped: booted, devices: booted ? [{ serial: state.adbSerial, status: 'device', emulator: true }] : [], }; - } catch { + } catch (error) { + if (options.signal?.aborted) throw error; return base; } } // ── Lifecycle ───────────────────────────────────────────────────────────── - async requestStartEmulator() { + async requestStartEmulator(options = {}) { console.log(`[Android] requestStartEmulator for user ${this.userId}`); const state = readState(this.userId); if (state.adbSerial && this.#isPidAlive(state.pid)) { console.log(`[Android] Emulator already running (pid=${state.pid})`); - return { success: true, pending: false, adbSerial: state.adbSerial }; + const status = await this.getStatus(options); + return { + success: true, + pending: !status.bootstrapped, + bootstrapped: status.bootstrapped, + adbSerial: state.adbSerial, + }; } if (!this.startPromise) { writeState(this.userId, { starting: true, startupPhase: 'Initializing', lastStartError: null }); - this.startPromise = this.#setup().finally(() => { this.startPromise = null; }); - this.startPromise.catch(() => {}); + const startAbortController = new AbortController(); + this.startAbortController = startAbortController; + const externalSignal = options.signal || null; + const forwardAbort = () => startAbortController.abort(externalSignal.reason); + if (externalSignal?.aborted) forwardAbort(); + else externalSignal?.addEventListener('abort', forwardAbort, { once: true }); + let trackedStart; + trackedStart = this.#setup({ + ...options, + signal: startAbortController.signal, + }).finally(() => { + externalSignal?.removeEventListener('abort', forwardAbort); + if (this.startAbortController === startAbortController) { + this.startAbortController = null; + } + if (this.startPromise === trackedStart) this.startPromise = null; + }); + this.startPromise = trackedStart; + trackedStart.catch(() => {}); } const s = readState(this.userId); return { success: true, pending: true, bootstrapped: false, starting: true, startupPhase: s.startupPhase }; } - async stopEmulator() { + async startEmulator(options = {}) { + const start = await this.requestStartEmulator(options); + if (start.bootstrapped) return start; + const timeoutMs = clampNumber(options.timeoutMs, 240_000, 1000, 10 * 60 * 1000); + const adbSerial = await this.waitForDevice({ timeoutMs, signal: options.signal }); + return { success: true, pending: false, bootstrapped: true, adbSerial }; + } + + async stopEmulator(options = {}) { + this.startAbortController?.abort(new Error('Android emulator startup was stopped.')); + const starting = this.startPromise; + if (starting) await starting.catch(() => {}); const state = readState(this.userId); - if (state.pid) { try { process.kill(Number(state.pid), 'SIGTERM'); } catch {} } - writeState(this.userId, { bootstrapped: false, starting: false, pid: null, adbSerial: null, startupPhase: null }); + let adbStopError = null; + if (state.adbSerial) { + try { + await runProcess( + adbBin(this.sdkDir), + ['-s', state.adbSerial, 'emu', 'kill'], + { timeoutMs: 5000, maxOutputBytes: 64 * 1024, signal: options.signal }, + ); + } catch (error) { + adbStopError = error; + } + } + await this.#terminateOwnedEmulatorProcess(); + if (state.pid) await this.#waitForPidExit(state.pid, 5000); + if (state.pid && this.#isPidAlive(state.pid)) { + if (options.signal?.aborted) throw abortError(options.signal); + if (adbStopError) throw adbStopError; + throw new Error('Android emulator did not exit after the stop request.'); + } + RESERVED_ADB_PORTS.delete(Number(String(state.adbSerial || '').replace(/^emulator-/, ''))); + RESERVED_ADB_PORTS.delete(this.adbPort); + writeState(this.userId, { + bootstrapped: false, + starting: false, + pid: null, + adbSerial: null, + startupPhase: null, + lastStartError: null, + }); + if (options.signal?.aborted) throw abortError(options.signal); console.log('[Android] Emulator stopped'); + return { success: true }; } async close() { await this.stopEmulator().catch(() => {}); } async waitForDevice(options = {}) { - const deadline = Date.now() + (options.timeoutMs || 600000); + const timeoutMs = clampNumber(options.timeoutMs, 600_000, 1000, 10 * 60 * 1000); + const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { - const s = await this.getStatus(); + if (options.signal?.aborted) { + const error = new Error('Waiting for the Android emulator was aborted.'); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + throw error; + } + const s = await this.getStatus(options); if (s.bootstrapped) return this.adbSerial; - await new Promise(r => setTimeout(r, 2000)); + if (!s.starting && s.lastStartError) throw new Error(s.lastStartError); + await delay(2000, options.signal); } throw new Error('Android emulator did not become ready in time'); } - async listDevices() { - const s = await this.getStatus(); + async listDevices(options = {}) { + const s = await this.getStatus(options); return s.bootstrapped ? [{ serial: this.adbSerial, status: 'device', emulator: true }] : []; } - async ensureBootstrapped() { - const s = readState(this.userId); - if (!s.bootstrapped) await this.requestStartEmulator(); + async ensureBootstrapped(options = {}) { + const status = await this.getStatus(options); + if (!status.bootstrapped) await this.startEmulator(options); + return this.adbSerial; } // ── Shell / ADB ─────────────────────────────────────────────────────────── async shell(commandOrObj) { const command = typeof commandOrObj === 'string' ? commandOrObj : String(commandOrObj?.command || ''); + if (!command.trim()) throw new Error('Android shell command is required.'); const serial = this.#requireSerial(); const adb = adbBin(this.sdkDir); - return new Promise((resolve, reject) => { - const proc = spawn(adb, ['-s', serial, 'shell', command], { encoding: 'utf8' }); - let out = '', err = ''; - proc.stdout?.on('data', d => { out += d; }); - proc.stderr?.on('data', d => { err += d; }); - proc.on('close', code => code === 0 ? resolve(out) : reject(new Error(err || out || `exit ${code}`))); - proc.on('error', reject); + const options = typeof commandOrObj === 'object' && commandOrObj ? commandOrObj : {}; + const result = await runProcess(adb, ['-s', serial, 'shell', command], { + timeoutMs: options.timeoutMs, + maxOutputBytes: 4 * 1024 * 1024, + signal: options.signal, }); + if (options.screenshot === true) { + return { output: result.stdout, ...await this.screenshot({ signal: options.signal }) }; + } + return result.stdout; } async adb(...args) { const state = readState(this.userId); const adb = adbBin(this.sdkDir); - return new Promise((resolve, reject) => { - const proc = spawn(adb, ['-s', state.adbSerial || this.adbSerial, ...args], { encoding: 'utf8' }); - let out = '', err = ''; - proc.stdout?.on('data', d => { out += d; }); - proc.stderr?.on('data', d => { err += d; }); - proc.on('close', code => code === 0 ? resolve(out) : reject(new Error(err || `adb ${args[0]} exit ${code}`))); - proc.on('error', reject); - }); + const result = await runProcess( + adb, + ['-s', state.adbSerial || this.adbSerial, ...args], + { timeoutMs: 60_000, maxOutputBytes: 16 * 1024 * 1024 }, + ); + return result.stdout; } // ── Actions ─────────────────────────────────────────────────────────────── - async screenshot(_opts = {}) { - const r = await this.capturePng(); + async screenshot(options = {}) { + const r = await this.capturePng(options); if (!r?.length) throw new Error('screencap returned no data'); - return { screenshotPath: this.#saveArtifact(r) }; + return { screenshotPath: await this.#saveArtifact(r, options) }; } - async capturePng(_opts = {}) { + async capturePng(options = {}) { const serial = this.#requireSerial(); - return this.#adbCaptureAsync(serial, ['exec-out', 'screencap', '-p']); + return this.#adbCaptureAsync(serial, ['exec-out', 'screencap', '-p'], options); } - async observe(_opts = {}) { return this.screenshot(); } + async observe({ includeNodes = true, signal } = {}) { + const screenshot = await this.screenshot({ signal }); + const dump = await this.dumpUi({ includeNodes, signal }); + return { + ...screenshot, + uiDumpPath: dump.devicePath, + nodeCount: dump.nodeCount, + ...(includeNodes === false ? {} : { nodes: dump.nodes }), + }; + } - async tap({ x, y } = {}) { - await this.shell(`input tap ${Math.round(x)} ${Math.round(y)}`); - return { success: true, screenshotPath: this.#saveArtifact(this.#adbCapture(this.#requireSerial(), ['exec-out', 'screencap', '-p'])) }; + async findUiNode(selector = {}) { + if (!hasUiSelector(selector)) { + throw new Error('Provide x and y coordinates or a UI selector.'); + } + const dump = await this.dumpUi({ includeNodes: true, signal: selector.signal }); + const node = findBestNode(dump.nodes, selector); + if (!node) { + throw new Error(`No Android UI element matched ${JSON.stringify(selector)}.`); + } + return node; } - async longPress({ x, y, durationMs = 1000 } = {}) { - await this.shell(`input swipe ${Math.round(x)} ${Math.round(y)} ${Math.round(x)} ${Math.round(y)} ${durationMs}`); - return { success: true }; + async resolvePoint(options = {}) { + const hasX = Number.isFinite(Number(options.x)); + const hasY = Number.isFinite(Number(options.y)); + if (hasX || hasY) { + if (!hasX || !hasY) throw new Error('Both x and y coordinates are required.'); + return { + x: requireCoordinate(options.x, 'x'), + y: requireCoordinate(options.y, 'y'), + node: null, + }; + } + const node = await this.findUiNode(options); + if (node.bounds.width <= 0 || node.bounds.height <= 0) { + throw new Error('The matched Android UI element has no tappable bounds.'); + } + return { x: node.bounds.centerX, y: node.bounds.centerY, node }; } - async swipe({ x1, y1, x2, y2, durationMs = 300 } = {}) { - await this.shell(`input swipe ${Math.round(x1)} ${Math.round(y1)} ${Math.round(x2)} ${Math.round(y2)} ${durationMs}`); - return { success: true, screenshotPath: this.#saveArtifact(this.#adbCapture(this.#requireSerial(), ['exec-out', 'screencap', '-p'])) }; + async tap(options = {}) { + const target = await this.resolvePoint(options); + await this.shell({ command: `input tap ${target.x} ${target.y}`, signal: options.signal }); + return { + success: true, + target: target.node ? summarizeNode(target.node) : { x: target.x, y: target.y }, + ...await this.screenshot({ signal: options.signal }), + }; } - async type({ text, pressEnter } = {}) { - if (!text) return { success: true }; + async longPress(options = {}) { + const target = await this.resolvePoint(options); + const durationMs = clampNumber(options.durationMs, 650, 100, 30_000); + await this.shell({ + command: `input swipe ${target.x} ${target.y} ${target.x} ${target.y} ${durationMs}`, + signal: options.signal, + }); + return { + success: true, + target: target.node ? summarizeNode(target.node) : { x: target.x, y: target.y }, + }; + } + + async swipe({ x1, y1, x2, y2, durationMs = 300, signal } = {}) { + const points = { + x1: requireCoordinate(x1, 'x1'), + y1: requireCoordinate(y1, 'y1'), + x2: requireCoordinate(x2, 'x2'), + y2: requireCoordinate(y2, 'y2'), + }; + const duration = clampNumber(durationMs, 300, 50, 30_000); + await this.shell({ + command: `input swipe ${points.x1} ${points.y1} ${points.x2} ${points.y2} ${duration}`, + signal, + }); + return { success: true, ...await this.screenshot({ signal }) }; + } + + async type({ text, textSelector, resourceId, description, className, clear = false, pressEnter, signal } = {}) { + if (text == null) throw new Error('Text is required.'); + if (String(text).length > MAX_ANDROID_TEXT_CHARS) { + throw new Error(`Text exceeds the ${MAX_ANDROID_TEXT_CHARS}-character limit.`); + } + const selector = { + text: textSelector, + resourceId, + description, + className, + signal, + }; + let target = null; + if (hasUiSelector(selector)) { + target = await this.resolvePoint(selector); + await this.shell({ command: `input tap ${target.x} ${target.y}`, signal }); + } + if (clear === true) { + await this.shell({ + command: 'input keyevent KEYCODE_MOVE_END; for i in $(seq 1 200); do input keyevent KEYCODE_DEL; done', + signal, + }); + } // ADB input text encoding: %% = literal %, %s = space. const encoded = String(text).replace(/%/g, '%%').replace(/ /g, '%s'); - await this.shell(`input text '${shellEscape(encoded)}'`); - if (pressEnter) await this.shell('input keyevent KEYCODE_ENTER'); - return { success: true }; + if (encoded) await this.shell({ command: `input text '${shellEscape(encoded)}'`, signal }); + if (pressEnter) await this.shell({ command: 'input keyevent KEYCODE_ENTER', signal }); + return { + success: true, + ...(target?.node ? { target: summarizeNode(target.node) } : {}), + }; } async pressKey(keyOrObj) { const raw = typeof keyOrObj === 'string' ? keyOrObj : (keyOrObj?.key || ''); + const signal = typeof keyOrObj === 'object' ? keyOrObj?.signal : null; const KEY_MAP = { back: 'KEYCODE_BACK', home: 'KEYCODE_HOME', app_switch: 'KEYCODE_APP_SWITCH', enter: 'KEYCODE_ENTER', del: 'KEYCODE_DEL', escape: 'KEYCODE_ESCAPE', menu: 'KEYCODE_MENU', power: 'KEYCODE_POWER', volume_up: 'KEYCODE_VOLUME_UP', volume_down: 'KEYCODE_VOLUME_DOWN', }; - const keycode = KEY_MAP[raw.toLowerCase()] || raw.toUpperCase(); - await this.shell(`input keyevent ${keycode}`); + const normalized = String(raw || '').trim(); + const keycode = KEY_MAP[normalized.toLowerCase()] + || (/^\d+$/.test(normalized) ? normalized : normalized.toUpperCase()); + if (!/^\d+$/.test(keycode) && !/^KEYCODE_[A-Z0-9_]+$/.test(keycode)) { + throw new Error(`Unsupported Android key: ${normalized || '(empty)'}`); + } + await this.shell({ command: `input keyevent ${keycode}`, signal }); return { success: true }; } - async dumpUi(_opts = {}) { - const serial = this.#requireSerial(); - await this.shell('uiautomator dump /sdcard/window_dump.xml'); - const r = spawnSync(adbBin(this.sdkDir), ['-s', serial, 'shell', 'cat', '/sdcard/window_dump.xml'], { encoding: 'utf8', timeout: 10000 }); - return { xml: r.stdout || '' }; + async dumpUi({ includeNodes = true, signal } = {}) { + this.#requireSerial(); + const devicePath = '/sdcard/window_dump.xml'; + await this.shell({ command: 'uiautomator dump /sdcard/window_dump.xml', signal }); + const xml = await this.shell({ command: `cat '${devicePath}'`, timeoutMs: 10_000, signal }); + const nodes = parseUiDump(xml); + return { + xml, + devicePath, + nodeCount: nodes.length, + ...(includeNodes === false ? {} : { nodes: nodes.slice(0, 200).map(summarizeNode) }), + }; } - async listApps({ includeSystem = false } = {}) { - const out = await this.shell(includeSystem ? 'pm list packages' : 'pm list packages -3'); + async listApps({ includeSystem = false, signal } = {}) { + const out = await this.shell({ + command: includeSystem ? 'pm list packages' : 'pm list packages -3', + signal, + }); const packages = out.trim().split('\n').filter(Boolean).map(l => l.replace('package:', '').trim()); return { packages }; } - async openApp({ packageName } = {}) { + async openApp({ packageName, activity, signal } = {}) { if (!isSafeIdentifier(packageName)) throw new Error('Invalid package name'); - await this.shell(`monkey -p '${shellEscape(packageName)}' -c android.intent.category.LAUNCHER 1`); - await new Promise(r => setTimeout(r, 1500)); - return this.screenshot(); + if (activity) { + if (!isSafeActivity(activity)) throw new Error('Invalid Android activity name'); + await this.shell({ command: `am start -n '${shellEscape(`${packageName}/${activity}`)}'`, signal }); + } else { + await this.shell({ + command: `monkey -p '${shellEscape(packageName)}' -c android.intent.category.LAUNCHER 1`, + signal, + }); + } + await delay(1500, signal); + return this.screenshot({ signal }); } - async openIntent({ action, dataUri, extras = {} } = {}) { + async openIntent({ action, dataUri, data, url, uri, packageName, component, mimeType, extras = {}, signal } = {}) { const safeAction = isSafeIdentifier(action) ? action : 'android.intent.action.VIEW'; let cmd = `am start -a '${shellEscape(safeAction)}'`; - if (dataUri) cmd += ` -d '${shellEscape(dataUri)}'`; - for (const [k, v] of Object.entries(extras || {})) { - if (isSafeIdentifier(k)) cmd += ` --es '${shellEscape(k)}' '${shellEscape(v)}'`; + const resolvedDataUri = dataUri || data || url || uri; + if (resolvedDataUri) { + if (String(resolvedDataUri).length > MAX_ANDROID_TEXT_CHARS) { + throw new Error('Android intent URI is too long.'); + } + const validation = await validateAndroidIntentUrl(String(resolvedDataUri), { signal }); + if (!validation.allowed) throw new Error('This Android intent URI is not permitted.'); + cmd += ` -d '${shellEscape(resolvedDataUri)}'`; + } + if (packageName) { + if (!isSafeIdentifier(packageName)) throw new Error('Invalid Android package name'); + cmd += ` -p '${shellEscape(packageName)}'`; + } + if (component) { + if (!isSafeComponent(component)) throw new Error('Invalid Android component name'); + cmd += ` -n '${shellEscape(component)}'`; + } + if (mimeType) { + if (!isSafeMimeType(mimeType)) throw new Error('Invalid Android MIME type'); + cmd += ` -t '${shellEscape(mimeType)}'`; + } + const extraEntries = Object.entries(extras || {}); + if (extraEntries.length > MAX_ANDROID_INTENT_EXTRAS) { + throw new Error(`Android intent extras exceed the ${MAX_ANDROID_INTENT_EXTRAS}-item limit.`); } - await this.shell(cmd); - await new Promise(r => setTimeout(r, 2000)); - return this.screenshot(); + for (const [k, v] of extraEntries) { + if (!isSafeIdentifier(k)) throw new Error(`Invalid Android intent extra name: ${k}`); + if (String(v).length > MAX_ANDROID_TEXT_CHARS) { + throw new Error(`Android intent extra is too long: ${k}`); + } + cmd += ` --es '${shellEscape(k)}' '${shellEscape(v)}'`; + } + await this.shell({ command: cmd, signal }); + await delay(2000, signal); + return this.screenshot({ signal }); } - async waitFor({ timeout = 10000 } = {}) { - const deadline = Date.now() + timeout; + async waitFor(options = {}) { + if (!hasUiSelector(options)) { + throw new Error('android_wait_for requires at least one UI selector.'); + } + const timeoutMs = clampNumber(options.timeoutMs ?? options.timeout, 20_000, 100, 120_000); + const intervalMs = clampNumber(options.intervalMs, 1500, 100, 5000); + const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { - const s = await this.getStatus(); - if (s.bootstrapped) return { ready: true }; - await new Promise(r => setTimeout(r, 1000)); + if (options.signal?.aborted) { + throw abortError(options.signal, 'Waiting for the Android UI was aborted.'); + } + const dump = await this.dumpUi({ includeNodes: true, signal: options.signal }); + const node = findBestNode(dump.nodes, options); + if (node) { + return { + found: true, + ready: true, + node: summarizeNode(node), + ...(options.screenshot === false ? {} : await this.screenshot({ signal: options.signal })), + }; + } + await delay(intervalMs, options.signal); } - return { ready: false }; + return { found: false, ready: false, timeoutMs }; } - async installApk({ apkPath } = {}) { + async installApk({ apkPath, signal } = {}) { if (!apkPath) throw new Error('apkPath required'); + const resolvedPath = fs.realpathSync(apkPath); + const packageStat = fs.statSync(resolvedPath); + if (!packageStat.isFile()) throw new Error('Android package path must be a file.'); + if (packageStat.size > MAX_ANDROID_PACKAGE_BYTES) { + throw new Error('Android package exceeds the 1GB installation limit.'); + } + const extension = path.extname(resolvedPath).toLowerCase(); + if (extension !== '.apk' && extension !== '.apks') { + throw new Error('Android package must be an .apk or universal .apks bundle.'); + } const serial = this.#requireSerial(); const adb = adbBin(this.sdkDir); - return new Promise((resolve, reject) => { - const proc = spawn(adb, ['-s', serial, 'install', '-r', apkPath]); - let out = '', err = ''; - proc.stdout?.on('data', d => { out += d; }); - proc.stderr?.on('data', d => { err += d; }); - proc.on('close', code => { - if (code === 0 && out.includes('Success')) resolve({ success: true, output: out }); - else reject(new Error(err || out || `adb install exit ${code}`)); + let installPath = resolvedPath; + let extractionDir = null; + try { + if (extension === '.apks') { + extractionDir = fs.mkdtempSync(path.join(os.tmpdir(), 'neoagent-apks-')); + const listing = await runProcess('unzip', ['-l', resolvedPath, 'universal.apk'], { + timeoutMs: 30_000, + maxOutputBytes: 1024 * 1024, + signal, + }); + const universalSize = String(listing.stdout) + .split(/\r?\n/) + .map((line) => line.trim().match(/^(\d+)\s+.*\suniversal\.apk$/i)) + .find(Boolean); + if (!universalSize) { + throw new Error('The .apks bundle does not contain universal.apk. Export it with bundletool --mode=universal.'); + } + if (Number(universalSize[1]) > MAX_ANDROID_PACKAGE_BYTES) { + throw new Error('The universal APK exceeds the 1GB installation limit.'); + } + await runProcess('unzip', ['-jo', resolvedPath, 'universal.apk', '-d', extractionDir], { + timeoutMs: 120_000, + maxOutputBytes: 1024 * 1024, + signal, + }); + installPath = path.join(extractionDir, 'universal.apk'); + if (!fs.existsSync(installPath)) { + throw new Error('The .apks bundle does not contain universal.apk. Export it with bundletool --mode=universal.'); + } + } + const result = await runProcess(adb, ['-s', serial, 'install', '-r', installPath], { + timeoutMs: 5 * 60 * 1000, + maxOutputBytes: 4 * 1024 * 1024, + signal, }); - proc.on('error', reject); - }); + if (!String(result.stdout).includes('Success')) { + throw new Error(String(result.stderr || result.stdout || 'adb install did not report success')); + } + return { success: true, output: result.stdout }; + } finally { + if (extractionDir) fs.rmSync(extractionDir, { recursive: true, force: true }); + } } // ── Private helpers ─────────────────────────────────────────────────────── @@ -468,52 +900,30 @@ class AndroidController { try { process.kill(Number(pid), 0); return true; } catch { return false; } } - #adbCapture(serial, args) { - const r = spawnSync(adbBin(this.sdkDir), ['-s', serial, ...args], { maxBuffer: 20 * 1024 * 1024, timeout: 15000 }); - return (r.status === 0 && r.stdout?.length) ? r.stdout : null; - } - - #adbCaptureAsync(serial, args) { - return new Promise((resolve, reject) => { - const proc = spawn(adbBin(this.sdkDir), ['-s', serial, ...args]); - const chunks = []; - const errors = []; - let settled = false; - const timer = setTimeout(() => { - if (settled) return; - settled = true; - try { proc.kill('SIGKILL'); } catch {} - reject(new Error('adb capture timed out')); - }, 15000); - timer.unref?.(); - proc.stdout?.on('data', (chunk) => chunks.push(chunk)); - proc.stderr?.on('data', (chunk) => errors.push(chunk)); - proc.on('error', (error) => { - if (settled) return; - settled = true; - clearTimeout(timer); - reject(error); - }); - proc.on('close', (code) => { - if (settled) return; - settled = true; - clearTimeout(timer); - if (code === 0 && chunks.length) { - resolve(Buffer.concat(chunks)); - return; - } - const message = Buffer.concat(errors).toString('utf8').trim(); - reject(new Error(message || `adb capture exit ${code}`)); - }); + async #adbCaptureAsync(serial, args, options = {}) { + const result = await runProcess(adbBin(this.sdkDir), ['-s', serial, ...args], { + timeoutMs: 15_000, + maxOutputBytes: 20 * 1024 * 1024, + encoding: null, + signal: options.signal, }); + if (!result.stdout.length) throw new Error('ADB capture returned no data.'); + return result.stdout; } - #saveArtifact(data) { + async #saveArtifact(data, options = {}) { if (!data || !this.artifactStore) return null; - const alloc = this.artifactStore.allocateFile(this.userId, { kind: 'screenshot', extension: 'png', contentType: 'image/png' }); - fs.writeFileSync(alloc.storagePath, data); - const fin = this.artifactStore.finalizeFile(alloc.artifactId, alloc.storagePath); - return fin.url; + const image = validateImageBuffer(data, { allowedTypes: ['image/png'] }); + const artifact = await this.artifactStore.createBufferArtifact(this.userId, { + kind: 'android-screenshot', + backend: 'android-emulator', + extension: image.extension, + contentType: image.contentType, + filenameBase: 'android-emulator-screenshot', + content: image.buffer, + signal: options.signal, + }); + return artifact.url; } // ── Setup pipeline ──────────────────────────────────────────────────────── @@ -523,21 +933,59 @@ class AndroidController { for (let i = 0; i < ADB_PORT_SLOTS; i++) { const slot = (base + i) % ADB_PORT_SLOTS; const port = ADB_PORT_BASE + slot * 2; - const free = await new Promise(resolve => { - const srv = net.createServer(); - srv.listen(port, '127.0.0.1', () => srv.close(() => resolve(true))); - srv.on('error', () => resolve(false)); - }); + if (RESERVED_ADB_PORTS.has(port)) continue; + const free = await this.#isPortPairFree(port); if (free) { + RESERVED_ADB_PORTS.add(port); this.adbPort = port; this.adbSerial = `emulator-${port}`; return; } } - throw new Error(`No free ADB port in range ${ADB_PORT_BASE}–${ADB_PORT_BASE + ADB_PORT_SLOTS * 2}`); + throw new Error(`No free ADB port pair in range ${ADB_PORT_BASE}–${ADB_PORT_BASE + (ADB_PORT_SLOTS * 2) - 1}`); + } + + async #isPortPairFree(consolePort) { + const servers = []; + const listen = (port) => new Promise((resolve) => { + const server = net.createServer(); + server.unref(); + server.once('error', () => resolve(false)); + server.listen(port, '127.0.0.1', () => { + servers.push(server); + resolve(true); + }); + }); + try { + return await listen(consolePort) && await listen(consolePort + 1); + } finally { + await Promise.allSettled(servers.map((server) => new Promise((resolve) => server.close(resolve)))); + } } - async #setup() { + async #waitForPidExit(pid, timeoutMs) { + const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0); + while (this.#isPidAlive(pid) && Date.now() < deadline) { + await delay(100); + } + return !this.#isPidAlive(pid); + } + + async #terminateOwnedEmulatorProcess() { + const proc = this.emulatorProcess; + if (!proc) return; + if (proc.exitCode == null && proc.signalCode == null) { + try { proc.kill('SIGTERM'); } catch {} + await this.#waitForPidExit(proc.pid, 3000); + } + if (proc.pid && this.#isPidAlive(proc.pid)) { + try { proc.kill('SIGKILL'); } catch {} + await this.#waitForPidExit(proc.pid, 2000); + } + if (this.emulatorProcess === proc) this.emulatorProcess = null; + } + + async #setup(options = {}) { const progress = msg => { console.log(`[Android] ${msg}`); writeState(this.userId, { startupPhase: msg }); @@ -549,52 +997,113 @@ class AndroidController { this.sdkDir = existing; progress(`Found existing Android SDK at ${existing}`); } else { - progress('Downloading Android SDK…'); - await ensureSdk(this.sdkDir, progress); - await ensurePackages(this.sdkDir, progress); + progress('Preparing Android SDK…'); } - ensureAvd(this.sdkDir, this.avdName, progress); - await this.#startEmulatorProcess(progress); + await ensureSdkProvisioned(this.sdkDir, progress, options); + await ensureAvd(this.sdkDir, this.avdName, this.avdHome, progress, options); + await this.#startEmulatorProcess(progress, options); } catch (err) { console.error(`[Android] Setup failed: ${err.message}`); - writeState(this.userId, { starting: false, startupPhase: 'Failed', lastStartError: err.message }); + await this.#terminateOwnedEmulatorProcess(); + RESERVED_ADB_PORTS.delete(this.adbPort); + writeState(this.userId, { + bootstrapped: false, + starting: false, + startupPhase: 'Failed', + lastStartError: err.message, + pid: null, + adbSerial: null, + }); + throw err; } } - async #startEmulatorProcess(progress) { + async #startEmulatorProcess(progress, options = {}) { progress('Starting Android emulator…'); - const env = { ...process.env, ANDROID_SDK_ROOT: this.sdkDir, ANDROID_HOME: this.sdkDir }; - const proc = spawn(emulatorBin(this.sdkDir), [ + const env = { + ...process.env, + ANDROID_AVD_HOME: this.avdHome, + ANDROID_SDK_ROOT: this.sdkDir, + ANDROID_HOME: this.sdkDir, + }; + const emulatorArgs = [ '-avd', this.avdName, - '-no-window', '-no-audio', '-no-boot-anim', + '-no-audio', '-no-boot-anim', '-port', String(this.adbPort), - '-gpu', 'swiftshader_indirect', '-partition-size', '800', - ], { env, detached: false, stdio: ['ignore', 'pipe', 'pipe'] }); - - proc.stdout.on('data', d => console.log(`[Android/emu] ${d.toString().trimEnd()}`)); - proc.stderr.on('data', d => console.log(`[Android/emu] ${d.toString().trimEnd()}`)); - writeState(this.userId, { pid: proc.pid, adbSerial: this.adbSerial }); + ]; + if (options.headless !== false) emulatorArgs.splice(2, 0, '-no-window'); + const proc = spawn(emulatorBin(this.sdkDir), emulatorArgs, { + env, + detached: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + this.emulatorProcess = proc; - proc.on('exit', code => { + let launchOutput = ''; + const collectLaunchOutput = (chunk) => { + if (launchOutput.length >= 64 * 1024) return; + launchOutput += chunk.toString().slice(0, (64 * 1024) - launchOutput.length); + }; + proc.stdout.on('data', collectLaunchOutput); + proc.stderr.on('data', collectLaunchOutput); + writeState(this.userId, { pid: proc.pid || null, adbSerial: proc.pid ? this.adbSerial : null }); + + let bootReady = false; + let rejectProcessFailure; + const processFailure = new Promise((_, reject) => { rejectProcessFailure = reject; }); + proc.once('error', (error) => rejectProcessFailure(error)); + proc.on('exit', (code, exitSignal) => { console.log(`[Android] Emulator exited with code ${code}`); - writeState(this.userId, { bootstrapped: false, starting: false, pid: null }); + this.emulatorProcess = null; + RESERVED_ADB_PORTS.delete(this.adbPort); + const current = readState(this.userId); + if (Number(current.pid) === Number(proc.pid)) { + writeState(this.userId, { bootstrapped: false, starting: false, pid: null, adbSerial: null }); + } + if (!bootReady) { + const details = launchOutput.trim().slice(-4000); + rejectProcessFailure(new Error( + `Android emulator exited before boot completed (${exitSignal || (code ?? 'unknown')}).${details ? ` ${details}` : ''}`, + )); + } }); progress('Waiting for Android to boot (can take 2–5 min on first run)…'); // Abort early if the emulator process dies, instead of polling until timeout. - await this.#waitForBoot({ isAlive: () => this.#isPidAlive(proc.pid) }); + const cancelEmulator = () => { + try { proc.kill('SIGTERM'); } catch {} + }; + options.signal?.addEventListener('abort', cancelEmulator, { once: true }); + try { + await Promise.race([ + this.#waitForBoot({ + isAlive: () => this.#isPidAlive(proc.pid), + signal: options.signal, + }), + processFailure, + ]); + bootReady = true; + } finally { + options.signal?.removeEventListener('abort', cancelEmulator); + } writeState(this.userId, { bootstrapped: true, starting: false, startupPhase: null, lastStartError: null }); console.log(`[Android] Emulator ready on ${this.adbSerial}`); // Set wallpaper — best-effort, never fails the boot sequence. - this.#setWallpaper(this.adbSerial).catch(err => { - console.warn(`[Android] Wallpaper not set: ${err.message}`); - }); + try { + await this.#setWallpaper(this.adbSerial, { signal: options.signal }); + } catch (error) { + if (options.signal?.aborted) throw error; + console.warn(`[Android] Wallpaper not set: ${error.message}`); + } + if (!this.#isPidAlive(proc.pid)) { + throw new Error('Android emulator exited immediately after boot completed.'); + } } - async #waitForBoot({ timeoutMs = 10 * 60 * 1000, isAlive = () => true } = {}) { + async #waitForBoot({ timeoutMs = 10 * 60 * 1000, isAlive = () => true, signal } = {}) { const adb = adbBin(this.sdkDir); const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { @@ -602,41 +1111,65 @@ class AndroidController { throw new Error('Emulator process exited before Android finished booting (check virtualization/KVM support and the system image).'); } try { - const r = spawnSync(adb, ['-s', this.adbSerial, 'shell', 'getprop', 'sys.boot_completed'], { encoding: 'utf8', timeout: 5000 }); - if (r.stdout?.trim() === '1') return; - } catch {} - await new Promise(r => setTimeout(r, 3000)); + const result = await runProcess( + adb, + ['-s', this.adbSerial, 'shell', 'getprop', 'sys.boot_completed'], + { timeoutMs: 5000, maxOutputBytes: 64 * 1024, signal }, + ); + if (result.stdout.trim() === '1') return; + } catch (error) { + if (signal?.aborted) throw error; + } + await delay(3000, signal); } throw new Error('Emulator did not boot within timeout'); } - async #setWallpaper(serial) { + async #setWallpaper(serial, options = {}) { if (!fs.existsSync(LOGO_PATH)) return; const adb = adbBin(this.sdkDir); // Try to gain root access (works on AOSP default images). - spawnSync(adb, ['-s', serial, 'root'], { timeout: 5000 }); - await new Promise(r => setTimeout(r, 1500)); + await runProcess(adb, ['-s', serial, 'root'], { + timeoutMs: 5000, + maxOutputBytes: 64 * 1024, + signal: options.signal, + }).catch(() => {}); + await delay(1500, options.signal); // Push PNG to device sdcard. - const push = spawnSync(adb, ['-s', serial, 'push', LOGO_PATH, '/sdcard/neoagent-wallpaper.png'], { timeout: 15000 }); - if (push.status !== 0) throw new Error('adb push logo failed'); + await runProcess(adb, ['-s', serial, 'push', LOGO_PATH, '/sdcard/neoagent-wallpaper.png'], { + timeoutMs: 15_000, + maxOutputBytes: 1024 * 1024, + signal: options.signal, + }); // cmd wallpaper set-stream reads PNG from stdin (Android 7.1+). const logoData = fs.readFileSync(LOGO_PATH); - const r = spawnSync(adb, ['-s', serial, 'shell', 'cmd', 'wallpaper', 'set-stream'], { - input: logoData, timeout: 15000, - }); - if (r.status === 0) { + try { + await runProcess(adb, ['-s', serial, 'shell', 'cmd', 'wallpaper', 'set-stream'], { + input: logoData, + timeoutMs: 15_000, + maxOutputBytes: 1024 * 1024, + signal: options.signal, + }); console.log('[Android] Wallpaper set'); return; - } + } catch {} // Fallback: direct file copy for rooted images (Android 11 AOSP). - spawnSync(adb, ['-s', serial, 'shell', 'cp /sdcard/neoagent-wallpaper.png /data/system/users/0/wallpaper'], { timeout: 5000 }); - spawnSync(adb, ['-s', serial, 'shell', 'chmod 600 /data/system/users/0/wallpaper'], { timeout: 5000 }); - spawnSync(adb, ['-s', serial, 'shell', 'chown system:system /data/system/users/0/wallpaper'], { timeout: 5000 }); - spawnSync(adb, ['-s', serial, 'shell', 'am broadcast -a android.intent.action.WALLPAPER_CHANGED'], { timeout: 5000 }); + for (const command of [ + 'cp /sdcard/neoagent-wallpaper.png /data/system/users/0/wallpaper', + 'chmod 600 /data/system/users/0/wallpaper', + 'chown system:system /data/system/users/0/wallpaper', + 'am broadcast -a android.intent.action.WALLPAPER_CHANGED', + ]) { + await runProcess(adb, ['-s', serial, 'shell', command], { + timeoutMs: 5000, + maxOutputBytes: 1024 * 1024, + signal: options.signal, + }); + } console.log('[Android] Wallpaper set via direct copy'); } } diff --git a/server/services/android/process.js b/server/services/android/process.js new file mode 100644 index 00000000..5dca0ca8 --- /dev/null +++ b/server/services/android/process.js @@ -0,0 +1,140 @@ +'use strict'; + +const { spawn } = require('node:child_process'); + +const DEFAULT_TIMEOUT_MS = 20_000; +const DEFAULT_MAX_OUTPUT_BYTES = 4 * 1024 * 1024; +const MAX_TIMEOUT_MS = 30 * 60 * 1000; + +function clampNumber(value, fallback, min, max) { + const parsed = Number(value); + if (!Number.isFinite(parsed)) return fallback; + return Math.min(max, Math.max(min, Math.round(parsed))); +} + +function createProcessError(message, code, details = {}) { + const error = new Error(message); + error.code = code; + Object.assign(error, details); + return error; +} + +function runProcess(command, args = [], options = {}) { + const timeoutMs = clampNumber(options.timeoutMs, DEFAULT_TIMEOUT_MS, 100, MAX_TIMEOUT_MS); + const maxOutputBytes = clampNumber( + options.maxOutputBytes, + DEFAULT_MAX_OUTPUT_BYTES, + 1024, + 1024 * 1024 * 1024, + ); + const encoding = options.encoding === null ? null : (options.encoding || 'utf8'); + + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: options.cwd, + env: options.env, + stdio: ['pipe', 'pipe', 'pipe'], + windowsHide: true, + }); + const stdout = []; + const stderr = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let settled = false; + let forceKillTimer = null; + + const decode = (chunks) => { + const buffer = Buffer.concat(chunks); + return encoding === null ? buffer : buffer.toString(encoding); + }; + const cleanup = () => { + clearTimeout(timeout); + options.signal?.removeEventListener('abort', onAbort); + }; + const settle = (error, result = null) => { + if (settled) return; + settled = true; + cleanup(); + if (error) reject(error); + else resolve(result); + }; + const terminate = (error) => { + try { child.kill('SIGTERM'); } catch {} + forceKillTimer = setTimeout(() => { + try { child.kill('SIGKILL'); } catch {} + }, 1000); + forceKillTimer.unref?.(); + settle(error); + }; + const onAbort = () => { + const error = options.signal?.reason instanceof Error + ? options.signal.reason + : createProcessError('Command was aborted.', 'ABORT_ERR'); + if (!(options.signal?.reason instanceof Error)) error.name = 'AbortError'; + terminate(error); + }; + const collect = (target, chunk, streamName) => { + if (settled) return; + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + if (streamName === 'stdout') stdoutBytes += buffer.length; + else stderrBytes += buffer.length; + if (stdoutBytes + stderrBytes > maxOutputBytes) { + terminate(createProcessError( + `Command exceeded the ${maxOutputBytes}-byte output limit.`, + 'PROCESS_OUTPUT_LIMIT', + )); + return; + } + target.push(buffer); + }; + + const timeout = setTimeout(() => { + terminate(createProcessError( + `Command timed out after ${timeoutMs} ms.`, + 'PROCESS_TIMEOUT', + { timeoutMs }, + )); + }, timeoutMs); + timeout.unref?.(); + + child.stdout?.on('data', (chunk) => collect(stdout, chunk, 'stdout')); + child.stderr?.on('data', (chunk) => collect(stderr, chunk, 'stderr')); + child.once('error', (error) => settle(error)); + child.once('close', (exitCode, signal) => { + if (forceKillTimer) clearTimeout(forceKillTimer); + if (settled) return; + const result = { + stdout: decode(stdout), + stderr: decode(stderr), + exitCode, + signal: signal || null, + }; + if (exitCode === 0) { + settle(null, result); + return; + } + const stderrText = Buffer.concat(stderr).toString('utf8').trim(); + const stdoutText = Buffer.concat(stdout).toString('utf8').trim(); + settle(createProcessError( + stderrText || stdoutText || `Command exited with code ${exitCode}.`, + 'PROCESS_EXIT', + result, + )); + }); + + if (options.signal?.aborted) { + onAbort(); + return; + } + options.signal?.addEventListener('abort', onAbort, { once: true }); + if (options.input != null) child.stdin?.end(options.input); + else child.stdin?.end(); + }); +} + +module.exports = { + DEFAULT_MAX_OUTPUT_BYTES, + DEFAULT_TIMEOUT_MS, + clampNumber, + runProcess, +}; diff --git a/server/services/android/sdk_download.js b/server/services/android/sdk_download.js new file mode 100644 index 00000000..2a59491f --- /dev/null +++ b/server/services/android/sdk_download.js @@ -0,0 +1,143 @@ +'use strict'; + +const crypto = require('node:crypto'); +const fs = require('node:fs'); +const https = require('node:https'); +const os = require('node:os'); +const { Transform } = require('node:stream'); +const { pipeline } = require('node:stream/promises'); +const { createAbortError } = require('../../utils/abort'); +const { clampNumber } = require('./process'); + +// Google publishes the current filenames and checksums at: +// https://developer.android.com/studio#command-line-tools-only +const COMMAND_LINE_TOOLS_VERSION = '15859902'; +const COMMAND_LINE_TOOLS_RELEASES = { + darwin: { + arm64: { + url: `https://dl.google.com/android/repository/commandlinetools-mac_arm64-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`, + sha256: '835b62a26162b229b441d1f6d4680383815a270809eb33522c0d480fa5002c4e', + }, + x64: { + url: `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`, + sha256: 'c5a6378ab5cf7e0d5701921405115befff13e9ff7417fb588389338f8bd050f3', + }, + }, + linux: { + url: `https://dl.google.com/android/repository/commandlinetools-linux-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`, + sha256: '4e4c464f145a7512b57d088ac6c278c03c9eea610886b35a5e0804e74eedf583', + }, + win32: { + url: `https://dl.google.com/android/repository/commandlinetools-win-${COMMAND_LINE_TOOLS_VERSION}_latest.zip`, + sha256: '90ae805d20434428bffcb699c290860f19bb5f66a67e6b330067e3de801fb04a', + }, +}; + +function resolveCommandLineToolsRelease(platform = process.platform, arch = os.arch()) { + const release = COMMAND_LINE_TOOLS_RELEASES[platform]; + if (!release) throw new Error(`No Android command-line-tools download for platform: ${platform}`); + if (platform !== 'darwin') return release; + if (arch !== 'arm64' && arch !== 'x64') { + throw new Error(`No Android command-line-tools download for macOS architecture: ${arch}`); + } + return release[arch]; +} + +function assertExpectedDigest(actualDigest, expectedDigest) { + const expected = String(expectedDigest || '').trim().toLowerCase(); + if (!/^[a-f0-9]{64}$/.test(expected)) { + throw new Error('Android SDK download is missing a valid SHA-256 checksum.'); + } + const actualBuffer = Buffer.from(actualDigest, 'hex'); + const expectedBuffer = Buffer.from(expected, 'hex'); + if (!crypto.timingSafeEqual(actualBuffer, expectedBuffer)) { + const error = new Error('Android SDK download failed SHA-256 verification.'); + error.code = 'ANDROID_SDK_CHECKSUM_MISMATCH'; + throw error; + } +} + +async function downloadFile(url, destination, options = {}) { + const maxRedirects = clampNumber(options.maxRedirects, 5, 0, 10); + const timeoutMs = clampNumber(options.timeoutMs, 60_000, 1000, 10 * 60 * 1000); + const maxBytes = clampNumber(options.maxBytes, 500 * 1024 * 1024, 1024, 1024 * 1024 * 1024); + const temporary = `${destination}.${process.pid}.${Math.random().toString(16).slice(2)}.part`; + + const openResponse = (target, redirectsLeft) => new Promise((resolve, reject) => { + if (options.signal?.aborted) { + reject(createAbortError(options.signal, 'Android SDK download was aborted.')); + return; + } + const parsed = new URL(target); + if (parsed.protocol !== 'https:') { + reject(new Error(`Refusing non-HTTPS Android SDK download: ${parsed.protocol}`)); + return; + } + const request = https.get(parsed, (response) => { + options.signal?.removeEventListener('abort', onAbort); + const status = Number(response.statusCode || 0); + if (status >= 300 && status < 400 && response.headers.location) { + response.resume(); + if (redirectsLeft <= 0) { + reject(new Error('Android SDK download exceeded its redirect limit.')); + return; + } + resolve(openResponse(new URL(response.headers.location, parsed).toString(), redirectsLeft - 1)); + return; + } + if (status !== 200) { + response.resume(); + reject(new Error(`Android SDK download failed with HTTP ${status}.`)); + return; + } + const contentLength = Number(response.headers['content-length'] || 0); + if (contentLength > maxBytes) { + response.destroy(); + reject(new Error(`Android SDK download exceeds the ${maxBytes}-byte limit.`)); + return; + } + resolve(response); + }); + const onAbort = () => request.destroy(createAbortError(options.signal, 'Android SDK download was aborted.')); + options.signal?.addEventListener('abort', onAbort, { once: true }); + request.setTimeout(timeoutMs, () => request.destroy(new Error('Android SDK download timed out.'))); + request.once('error', (error) => { + options.signal?.removeEventListener('abort', onAbort); + reject(error); + }); + }); + + try { + const response = await openResponse(url, maxRedirects); + const hash = crypto.createHash('sha256'); + let received = 0; + const verifier = new Transform({ + transform(chunk, _encoding, callback) { + received += chunk.length; + if (received > maxBytes) { + callback(new Error(`Android SDK download exceeds the ${maxBytes}-byte limit.`)); + return; + } + hash.update(chunk); + callback(null, chunk); + }, + }); + await pipeline(response, verifier, fs.createWriteStream(temporary, { mode: 0o600 }), { + signal: options.signal, + }); + assertExpectedDigest(hash.digest('hex'), options.expectedSha256); + fs.renameSync(temporary, destination); + } catch (error) { + if (options.signal?.aborted) { + throw createAbortError(options.signal, 'Android SDK download was aborted.'); + } + throw error; + } finally { + try { fs.unlinkSync(temporary); } catch {} + } +} + +module.exports = { + downloadFile, + resolveCommandLineToolsRelease, +}; diff --git a/server/services/android/uia.js b/server/services/android/uia.js index f129afc2..a5dacea9 100644 --- a/server/services/android/uia.js +++ b/server/services/android/uia.js @@ -12,12 +12,13 @@ const DEFAULT_BOUNDS = Object.freeze({ function decodeXml(value) { return String(value || '') .replace(/"/g, '"') - .replace(/&/g, '&') + .replace(/'/g, "'") .replace(/</g, '<') .replace(/>/g, '>') .replace(/ /g, '\n') .replace(/ /g, '\r') - .replace(/'/g, "'"); + .replace(/'/g, "'") + .replace(/&/g, '&'); } function parseBounds(raw) { @@ -54,7 +55,7 @@ function parseNodeAttributes(raw) { function parseUiDump(xml) { const nodes = []; - const nodeRe = /]*)\/>/g; + const nodeRe = /]*)>/g; let match = nodeRe.exec(String(xml || '')); while (match) { @@ -123,8 +124,8 @@ function scoreNode(node, selector = {}) { else return -1; } - if (selector.clickable === true && node.clickable) score += 40; - if (selector.clickable === true && !node.clickable) score -= 60; + if (selector.clickable === true && !node.clickable) return -1; + if (selector.clickable === true) score += 40; if (node.enabled) score += 10; if (node.bounds.width > 0 && node.bounds.height > 0) score += 10; diff --git a/server/services/artifacts/store.js b/server/services/artifacts/store.js index 84db5388..2cb15dc2 100644 --- a/server/services/artifacts/store.js +++ b/server/services/artifacts/store.js @@ -3,6 +3,7 @@ const path = require('path'); const { v4: uuidv4 } = require('uuid'); const db = require('../../db/database'); const { DATA_DIR } = require('../../../runtime/paths'); +const { writeBufferAtomic } = require('../../utils/files'); const ARTIFACTS_ROOT = path.join(DATA_DIR, 'artifacts'); fs.mkdirSync(ARTIFACTS_ROOT, { recursive: true }); @@ -121,6 +122,29 @@ class ArtifactStore { }; } + async createBufferArtifact(userId, options = {}) { + const buffer = Buffer.isBuffer(options.content) + ? options.content + : Buffer.from(options.content || []); + const allocation = this.allocateFile(userId, options); + try { + await writeBufferAtomic(allocation.storagePath, buffer, { + signal: options.signal, + }); + db.prepare('UPDATE artifacts SET byte_size = ? WHERE id = ?') + .run(buffer.length, allocation.artifactId); + return { + ...allocation, + filePath: allocation.storagePath, + byteSize: buffer.length, + }; + } catch (error) { + await fs.promises.rm(allocation.storagePath, { force: true }).catch(() => {}); + db.prepare('DELETE FROM artifacts WHERE id = ?').run(allocation.artifactId); + throw error; + } + } + getArtifactForUser(userId, artifactId) { const row = db.prepare(` SELECT id, user_id, kind, backend, content_type, storage_path, original_filename, byte_size, metadata_json, created_at diff --git a/server/services/browser/controller.js b/server/services/browser/controller.js index a4b25db3..a0221b9f 100644 --- a/server/services/browser/controller.js +++ b/server/services/browser/controller.js @@ -1,7 +1,11 @@ +'use strict'; + const path = require('path'); const fs = require('fs'); const { spawn } = require('child_process'); const { DATA_DIR } = require('../../../runtime/paths'); +const { validateCloudUrlWithDns } = require('../../utils/cloud-security'); +const { runProcess } = require('../android/process'); const { chooseBrowserIdentity, detectBotChallenge, @@ -127,40 +131,66 @@ function resolveBrowserExecutablePath() { return platformCandidates.find((candidate) => fs.existsSync(candidate)) || null; } -function installPlaywrightBrowserBinary(browserName) { +async function installPlaywrightBrowserBinary(browserName, options = {}) { const packageRoot = path.dirname(require.resolve('playwright-chromium/package.json')); const cliPath = path.join(packageRoot, 'cli.js'); - return new Promise((resolve, reject) => { - const args = [cliPath, 'install', '--no-shell', browserName]; - const child = spawn(process.execPath, args, { - stdio: ['ignore', 'pipe', 'pipe'], - }); - let stdout = ''; - let stderr = ''; + await runProcess(process.execPath, [cliPath, 'install', '--no-shell', browserName], { + signal: options.signal, + timeoutMs: 10 * 60 * 1000, + maxOutputBytes: 32 * 1024 * 1024, + }); +} - child.stdout.on('data', (data) => { - stdout += data.toString(); - }); - child.stderr.on('data', (data) => { - stderr += data.toString(); - }); - child.on('error', (error) => { - const detail = String(error?.message || `playwright install ${browserName} failed`).trim(); - reject(new Error(detail)); - }); - child.on('close', (code) => { - if (code === 0) { - resolve(); - return; - } - const detail = String(stderr || stdout || `playwright install ${browserName} exited with code ${code ?? 'unknown'}`).trim(); - reject(new Error(detail)); - }); +function createAbortError(signal, fallback = 'Browser operation aborted.') { + if (signal?.reason instanceof Error) return signal.reason; + const error = new Error(String(signal?.reason || fallback)); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +function throwIfAborted(signal) { + if (signal?.aborted) throw createAbortError(signal); +} + +function sleep(ms, signal = null) { + throwIfAborted(signal); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject(createAbortError(signal)); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + if (signal?.aborted) onAbort(); }); } -function sleep(ms) { - return new Promise(r => setTimeout(r, ms)); +function isAbortError(error, signal = null) { + return Boolean( + signal?.aborted + || error?.name === 'AbortError' + || error?.code === 'ABORT_ERR', + ); +} + +async function raceWithSignal(promise, signal) { + throwIfAborted(signal); + if (!signal) return promise; + let onAbort; + const aborted = new Promise((_, reject) => { + onAbort = () => reject(createAbortError(signal)); + signal.addEventListener('abort', onAbort, { once: true }); + if (signal.aborted) onAbort(); + }); + try { + return await Promise.race([promise, aborted]); + } finally { + signal.removeEventListener('abort', onAbort); + } } async function waitForFile(filePath, options = {}) { @@ -171,7 +201,7 @@ async function waitForFile(filePath, options = {}) { } const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { - await sleep(intervalMs); + await sleep(intervalMs, options.signal); if (fs.existsSync(filePath)) { return true; } @@ -197,6 +227,17 @@ function normalizeWaitUntil(waitUntil) { return 'domcontentloaded'; } +function normalizePointCoordinate(value, label) { + if (value == null || value === '') { + throw new Error(`${label} coordinate is required.`); + } + const normalized = Number(value); + if (!Number.isFinite(normalized)) { + throw new Error(`${label} coordinate must be a finite number.`); + } + return Math.round(normalized); +} + function clearChromiumSingletonLocks(profileDir) { const lockEntries = [ 'SingletonLock', @@ -213,6 +254,18 @@ function clearChromiumSingletonLocks(profileDir) { } } +function chooseVirtualDisplay() { + for (let number = 90; number < 200; number += 1) { + if ( + !fs.existsSync(`/tmp/.X11-unix/X${number}`) + && !fs.existsSync(`/tmp/.X${number}-lock`) + ) { + return `:${number}`; + } + } + throw new Error('No free X11 display is available for the browser.'); +} + class BrowserController { constructor(options = {}) { this.io = options.io || null; @@ -226,9 +279,15 @@ class BrowserController { this.page = null; this.displayProcess = null; this.displayValue = process.env.DISPLAY || null; + this._managedDisplayValue = null; this.launching = false; this.launchPromise = null; this.browserBinaryInstallPromise = null; + this._launchAbortController = null; + this._urlValidator = options.urlValidator || validateCloudUrlWithDns; + this._boundPages = new WeakSet(); + this._closing = false; + this._closePromise = null; this.headless = false; this.profileDir = path.join(BROWSER_PROFILE_ROOT, this.userId || 'default'); if (!fs.existsSync(this.profileDir)) fs.mkdirSync(this.profileDir, { recursive: true }); @@ -251,6 +310,122 @@ class BrowserController { return this.close(); } + _contextIsOpen() { + if (!this.context) return false; + if (typeof this.context.isClosed === 'function' && this.context.isClosed()) return false; + return !this.browser + || typeof this.browser.isConnected !== 'function' + || this.browser.isConnected(); + } + + _clearBrowserReferences(context, browser) { + if (context && this.context !== context) return; + if (!context && browser && this.browser !== browser) return; + this.context = null; + this.browser = null; + this.page = null; + } + + _bindPage(page, options = {}) { + if (!page) return; + if (!this._boundPages.has(page)) { + this._boundPages.add(page); + const clearPage = () => { + if (this.page === page) this.page = null; + }; + page.on?.('close', clearPage); + page.on?.('crash', clearPage); + } + if (options.makeActive !== false) this.page = page; + } + + _bindContextLifecycle(context, browser) { + context.on?.('close', () => this._clearBrowserReferences(context, browser)); + context.on?.('page', (page) => { + this._bindPage(page); + this._applyStealthToPage(page).catch(() => {}); + }); + browser?.on?.('disconnected', () => this._clearBrowserReferences(context, browser)); + for (const page of context.pages?.() || []) this._bindPage(page, { makeActive: false }); + } + + async _networkUrlAllowed(url) { + let parsed; + try { + parsed = new URL(String(url || '')); + } catch { + return false; + } + const protocol = parsed.protocol.toLowerCase(); + if (protocol === 'about:' && parsed.href === 'about:blank') return true; + if (protocol === 'blob:' || protocol === 'data:') return true; + if (protocol === 'ws:' || protocol === 'wss:') { + parsed.protocol = protocol === 'ws:' ? 'http:' : 'https:'; + } else if (protocol !== 'http:' && protocol !== 'https:') { + return false; + } + const result = await this._urlValidator(parsed.href); + return result?.allowed === true; + } + + async _assertNavigationAllowed(url, options = {}) { + let result = null; + try { + result = await this._urlValidator(String(url || ''), { signal: options.signal }); + } catch (error) { + if (isAbortError(error, options.signal)) throw error; + } + if (result?.allowed !== true) { + const error = new Error('This URL is not permitted.'); + error.code = 'URL_BLOCKED'; + throw error; + } + } + + async _installNetworkGuard(context) { + await context.route('**/*', async (route) => { + let allowed = false; + try { + allowed = await this._networkUrlAllowed(route.request().url()); + } catch {} + if (!allowed) { + await route.abort('blockedbyclient').catch(() => {}); + return; + } + await route.continue().catch(() => {}); + }); + + if (typeof context.routeWebSocket === 'function') { + await context.routeWebSocket('**', async (webSocket) => { + let allowed = false; + try { + allowed = await this._networkUrlAllowed(webSocket.url()); + } catch {} + if (!allowed) { + await webSocket.close({ code: 1008, reason: 'Blocked by network policy' }).catch(() => {}); + return; + } + webSocket.connectToServer(); + }); + } + } + + async _withPageCancellation(page, signal, operation) { + throwIfAborted(signal); + if (!signal) return operation(); + const closeOnAbort = () => { + if (!page?.isClosed?.()) { + page.close({ runBeforeUnload: false, reason: 'Browser operation cancelled' }).catch(() => {}); + } + }; + signal.addEventListener('abort', closeOnAbort, { once: true }); + try { + return await raceWithSignal(Promise.resolve().then(operation), signal); + } finally { + signal.removeEventListener('abort', closeOnAbort); + } + } + async _applyStealthToPage(page) { const ua = this._userAgent; const vp = this._viewport; @@ -308,7 +483,6 @@ class BrowserController { arr.item = i => arr[i]; arr.namedItem = n => arr.find(p => p.name === n) || null; arr.refresh = () => {}; - Object.defineProperty(arr, 'length', { get: () => arr.length }); return arr; } }); @@ -345,17 +519,6 @@ class BrowserController { return originalFillText.apply(this, args); }; - // Media Devices Spoofing - if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { - const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); - navigator.mediaDevices.enumerateDevices = async () => { - return [ - { kind: 'audioinput', deviceId: 'default', groupId: 'a', label: 'MacBook Pro Microphone' }, - { kind: 'audiooutput', deviceId: 'default', groupId: 'b', label: 'MacBook Pro Speakers' }, - { kind: 'videoinput', deviceId: 'default', groupId: 'c', label: 'FaceTime HD Camera' } - ]; - }; - } })(); `; if (typeof page.evaluateOnNewDocument === 'function') { @@ -365,23 +528,42 @@ class BrowserController { } } - async ensureBrowser() { - if (this.browser && this.browser.isConnected()) return; + async ensureBrowser(options = {}) { + let signal = options.signal; + throwIfAborted(signal); + if (this._closePromise) await raceWithSignal(this._closePromise, signal); + if (this._contextIsOpen()) return; if (this.launchPromise) { - await this.launchPromise; + await raceWithSignal(this.launchPromise, signal); return; } + const staleContext = this.context; + const staleBrowser = this.browser; + this._clearBrowserReferences(staleContext, staleBrowser); + if (staleContext) await staleContext.close().catch(() => {}); + else if (staleBrowser) await staleBrowser.close().catch(() => {}); + + const launchAbortController = new AbortController(); + this._launchAbortController = launchAbortController; + const externalSignal = signal; + const forwardAbort = () => launchAbortController.abort(externalSignal.reason); + externalSignal?.addEventListener('abort', forwardAbort, { once: true }); + if (externalSignal?.aborted) forwardAbort(); + signal = launchAbortController.signal; + this.launching = true; this.launchPromise = (async () => { const runtimeReady = await waitForFile(BROWSER_READY_MARKER, { timeoutMs: 10 * 60 * 1000, intervalMs: 1000, + signal, }); if (!runtimeReady) { throw new Error('Browser runtime provisioning is still in progress inside the VM. Retry shortly.'); } - await this.ensureVirtualDisplay(); + await this.ensureVirtualDisplay({ signal }); + throwIfAborted(signal); this._identity = chooseBrowserIdentity(this.userId || this.profileDir); this._userAgent = this._identity.userAgent; @@ -394,10 +576,10 @@ class BrowserController { let executablePath = resolveBrowserExecutablePath(); if (!executablePath) { if (!this.browserBinaryInstallPromise) { - this.browserBinaryInstallPromise = installPlaywrightBrowserBinary(this.engine); + this.browserBinaryInstallPromise = installPlaywrightBrowserBinary(this.engine, { signal }); } try { - await this.browserBinaryInstallPromise; + await raceWithSignal(this.browserBinaryInstallPromise, signal); } finally { this.browserBinaryInstallPromise = null; } @@ -444,7 +626,7 @@ class BrowserController { const playwright = require('playwright-chromium'); clearChromiumSingletonLocks(this.profileDir); - this.context = await playwright.chromium.launchPersistentContext(this.profileDir, { + const launchPromise = playwright.chromium.launchPersistentContext(this.profileDir, { headless: false, executablePath, env: launchEnv, @@ -453,98 +635,137 @@ class BrowserController { userAgent: this._userAgent, locale: 'en-US', ignoreHTTPSErrors: false, + serviceWorkers: 'block', timeout: 120000, }); - this.browser = typeof this.context.browser === 'function' ? this.context.browser() : null; + let context; + try { + context = await raceWithSignal(launchPromise, signal); + } catch (error) { + launchPromise.then((lateContext) => lateContext.close().catch(() => {})).catch(() => {}); + throw error; + } + const browser = typeof context.browser === 'function' ? context.browser() : null; + this.context = context; + this.browser = browser; + this._bindContextLifecycle(context, browser); // Cloud security: deny access to local devices on every page in this context. - await this.context.addInitScript(DEVICE_DENY_SCRIPT); + await context.addInitScript(DEVICE_DENY_SCRIPT); + await this._installNetworkGuard(context); - this.page = this.context.pages()[0] || await this.context.newPage(); + this.page = context.pages()[0] || await context.newPage(); + this._bindPage(this.page); await this._applyStealthToPage(this.page); })(); try { - await this.launchPromise; + await raceWithSignal(this.launchPromise, signal); + } catch (error) { + const failedContext = this.context; + const failedBrowser = this.browser; + this._clearBrowserReferences(failedContext, failedBrowser); + if (failedContext) await failedContext.close().catch(() => {}); + else if (failedBrowser) await failedBrowser.close().catch(() => {}); + await this._stopVirtualDisplay(); + throw error; } finally { + externalSignal?.removeEventListener('abort', forwardAbort); + if (this._launchAbortController === launchAbortController) { + this._launchAbortController = null; + } this.launchPromise = null; this.launching = false; } } - async ensurePage() { - await this.ensureBrowser(); + async ensurePage(options = {}) { + const signal = options.signal; + await this.ensureBrowser(options); + throwIfAborted(signal); if (!this.page || this.page.isClosed()) { + let pagePromise; if (this.context && typeof this.context.newPage === 'function') { - this.page = await this.context.newPage(); + pagePromise = this.context.newPage(); } else { - this.page = await this.browser.newPage(); + pagePromise = this.browser.newPage(); + } + try { + this.page = await raceWithSignal(pagePromise, signal); + } catch (error) { + pagePromise.then((latePage) => latePage.close().catch(() => {})).catch(() => {}); + throw error; } + this._bindPage(this.page); await this._applyStealthToPage(this.page); } return this.page; } async takeScreenshot(options = {}) { - const page = await this.ensurePage(); - let artifactRecord = null; - let filename = `screenshot_${Date.now()}.png`; - let filepath = path.join(SCREENSHOTS_DIR, filename); - if (this.artifactStore && this.userId != null) { - artifactRecord = this.artifactStore.allocateFile(this.userId, { - kind: 'browser-screenshot', - backend: this.runtimeBackend, - extension: 'png', - contentType: 'image/png', - filenameBase: 'browser-screenshot', - metadata: { - selector: options.selector || null, - fullPage: options.fullPage === true, - }, - }); - filepath = artifactRecord.storagePath; - filename = path.basename(filepath); - } + const page = await this.ensurePage(options); + return this._withPageCancellation(page, options.signal, async () => { + let artifactRecord = null; + let filename = `screenshot_${Date.now()}.png`; + let filepath = path.join(SCREENSHOTS_DIR, filename); + if (this.artifactStore && this.userId != null) { + artifactRecord = this.artifactStore.allocateFile(this.userId, { + kind: 'browser-screenshot', + backend: this.runtimeBackend, + extension: 'png', + contentType: 'image/png', + filenameBase: 'browser-screenshot', + metadata: { + selector: options.selector || null, + fullPage: options.fullPage === true, + }, + }); + filepath = artifactRecord.storagePath; + filename = path.basename(filepath); + } - const screenshotOptions = { path: filepath, type: 'png' }; - if (options.fullPage) screenshotOptions.fullPage = true; - if (options.selector) { - const element = await page.$(options.selector); - if (element) { - await element.screenshot(screenshotOptions); + const screenshotOptions = { path: filepath, type: 'png' }; + if (options.fullPage) screenshotOptions.fullPage = true; + if (options.selector) { + const element = await page.$(options.selector); + if (element) { + await element.screenshot(screenshotOptions); + } else { + await page.screenshot(screenshotOptions); + } } else { await page.screenshot(screenshotOptions); } - } else { - await page.screenshot(screenshotOptions); - } - if (artifactRecord) { - this.artifactStore.finalizeFile(artifactRecord.artifactId, filepath); - } + if (artifactRecord) { + this.artifactStore.finalizeFile(artifactRecord.artifactId, filepath); + } - return { - screenshotPath: artifactRecord ? artifactRecord.url : `/screenshots/${filename}`, - artifactId: artifactRecord?.artifactId || null, - filename, - fullPath: filepath, - }; + return { + screenshotPath: artifactRecord ? artifactRecord.url : `/screenshots/${filename}`, + artifactId: artifactRecord?.artifactId || null, + filename, + fullPath: filepath, + }; + }); } async screenshotJpeg(quality = 80, options = {}) { - const page = await this.ensurePage(); - const screenshotOptions = { - type: 'jpeg', - quality: Math.min(95, Math.max(30, Math.floor(Number(quality) || 80))), - fullPage: options.fullPage === true, - }; - if (options.selector) { - const element = await page.$(options.selector); - if (element) { - return element.screenshot(screenshotOptions); + const page = await this.ensurePage(options); + return this._withPageCancellation(page, options.signal, async () => { + const screenshotOptions = { + type: 'jpeg', + quality: Math.min(95, Math.max(30, Math.floor(Number(quality) || 80))), + fullPage: options.fullPage === true, + }; + if (options.selector) { + const element = await page.$(options.selector); + if (element) { + return element.screenshot(screenshotOptions); + } } - } - return page.screenshot(screenshotOptions); + return page.screenshot(screenshotOptions); + }); } async _navigatePage(page, url, options = {}) { @@ -552,9 +773,21 @@ class BrowserController { const waitUntil = normalizeWaitUntil(options.waitUntil); if (referrerMode === 'current' && page.url() && page.url() !== 'about:blank') { const previousUrl = page.url(); - await page.evaluate((nextUrl) => { window.location.href = nextUrl; }, url); - await page.waitForFunction((oldUrl) => window.location.href !== oldUrl, previousUrl, { timeout: 10000 }).catch(() => {}); - await page.waitForLoadState(waitUntil, { timeout: 30000 }).catch(() => {}); + const urlChanged = typeof page.waitForURL === 'function' + ? page.waitForURL( + (nextUrl) => String(nextUrl) !== previousUrl, + { waitUntil, timeout: 30000 }, + ) + : page.waitForFunction( + (oldUrl) => window.location.href !== oldUrl, + previousUrl, + { timeout: 30000 }, + ); + await Promise.all([ + urlChanged, + page.evaluate((nextUrl) => { window.location.href = nextUrl; }, url), + ]); + await page.waitForLoadState(waitUntil, { timeout: 30000 }); return null; } @@ -581,80 +814,89 @@ class BrowserController { } async navigate(url, options = {}) { - const page = await this.ensurePage(); - + let page = null; try { - const requestedReferrerMode = normalizeReferrerMode(options.referrerMode); - let activeReferrerMode = requestedReferrerMode; - let response = await this._navigatePage(page, url, { - ...options, - referrerMode: activeReferrerMode, - }); - - if (options.waitFor) { - await page.waitForSelector(options.waitFor, { timeout: 10000 }).catch(() => { }); - } - - // Simulate human reading delay - await sleep(rand(700, 1800)); - - let rawHtml = await page.content(); - const { extractForLLM } = require('./contentExtractor'); - let extraction = extractForLLM(rawHtml, { url: page.url() }); - let botDetection = await this._getBotDetection(page, rawHtml, extraction.markdown); - let challengeRetried = false; - - if ( - botDetection.detected - && normalizeChallengeRetry(options.challengeRetry) - && requestedReferrerMode === 'direct' - ) { - challengeRetried = true; - activeReferrerMode = 'google'; - await sleep(rand(1200, 2600)); - response = await this._navigatePage(page, url, { + await this._assertNavigationAllowed(url, options); + page = await this.ensurePage(options); + return await this._withPageCancellation(page, options.signal, async () => { + const requestedReferrerMode = normalizeReferrerMode(options.referrerMode); + let activeReferrerMode = requestedReferrerMode; + let response = await this._navigatePage(page, url, { ...options, referrerMode: activeReferrerMode, }); + if (options.waitFor) { - await page.waitForSelector(options.waitFor, { timeout: 10000 }).catch(() => { }); + await page.waitForSelector(options.waitFor, { timeout: 10000 }); } - await sleep(rand(900, 2200)); - rawHtml = await page.content(); - extraction = extractForLLM(rawHtml, { url: page.url() }); - botDetection = await this._getBotDetection(page, rawHtml, extraction.markdown); - } - const title = await page.title(); - const currentUrl = page.url(); + // Simulate human reading delay. + await sleep(rand(700, 1800), options.signal); + + let rawHtml = await page.content(); + const { extractForLLM } = require('./contentExtractor'); + let extraction = extractForLLM(rawHtml, { url: page.url() }); + let botDetection = await this._getBotDetection(page, rawHtml, extraction.markdown); + let challengeRetried = false; + + if ( + botDetection.detected + && normalizeChallengeRetry(options.challengeRetry) + && requestedReferrerMode === 'direct' + ) { + challengeRetried = true; + activeReferrerMode = 'google'; + await sleep(rand(1200, 2600), options.signal); + response = await this._navigatePage(page, url, { + ...options, + referrerMode: activeReferrerMode, + }); + if (options.waitFor) { + await page.waitForSelector(options.waitFor, { timeout: 10000 }); + } + await sleep(rand(900, 2200), options.signal); + rawHtml = await page.content(); + extraction = extractForLLM(rawHtml, { url: page.url() }); + botDetection = await this._getBotDetection(page, rawHtml, extraction.markdown); + } - let screenshot = null; - if (options.screenshot !== false) { - screenshot = await this.takeScreenshot({ fullPage: options.fullPage }); - } + const title = await page.title(); + const currentUrl = page.url(); - return { - title, - url: currentUrl, - status: response?.status() || 0, - pageContent: extraction.markdown, - botDetection, - referrerMode: activeReferrerMode, - challengeRetried, - screenshotPath: screenshot?.screenshotPath || null, - artifactId: screenshot?.artifactId || null, - fullPath: screenshot?.fullPath || null - }; + let screenshot = null; + if (options.screenshot !== false) { + screenshot = await this.takeScreenshot({ + fullPage: options.fullPage, + signal: options.signal, + }); + } + + return { + title, + url: currentUrl, + status: response?.status() || 0, + pageContent: extraction.markdown, + botDetection, + referrerMode: activeReferrerMode, + challengeRetried, + screenshotPath: screenshot?.screenshotPath || null, + artifactId: screenshot?.artifactId || null, + fullPath: screenshot?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; let screenshot = null; - try { screenshot = await this.takeScreenshot(); } catch { } + if (page && !page.isClosed()) { + try { screenshot = await this.takeScreenshot(); } catch {} + } return { error: err.message, url, botDetection: { detected: false, provider: null }, screenshotPath: screenshot?.screenshotPath || null, artifactId: screenshot?.artifactId || null, - fullPath: screenshot?.fullPath || null + fullPath: screenshot?.fullPath || null, }; } } @@ -668,7 +910,7 @@ class BrowserController { for (const point of path) { await page.mouse.move(point.x, point.y); if (!options.fast) { - await sleep(rand(2, 10)); + await sleep(rand(2, 10), options.signal); } } this._mousePosition = target; @@ -689,271 +931,327 @@ class BrowserController { }; } - async click(selector, text, screenshot = true) { - const page = await this.ensurePage(); + async click(selector, text, screenshot = true, options = {}) { + const page = await this.ensurePage(options); try { - let target = null; - - if (text && !selector) { - const elements = await page.$$('a, button, [role="button"], input[type="submit"], [onclick]'); - for (const el of elements) { - const elText = await page.evaluate(e => e.innerText || e.value || e.getAttribute('aria-label') || '', el); - if (elText.toLowerCase().includes(text.toLowerCase())) { - target = el; - break; + return await this._withPageCancellation(page, options.signal, async () => { + let target = null; + + if (text && !selector) { + const elements = await page.$$('a, button, [role="button"], input[type="submit"], [onclick]'); + for (const el of elements) { + const elText = await page.evaluate(e => e.innerText || e.value || e.getAttribute('aria-label') || '', el); + if (elText.toLowerCase().includes(String(text).toLowerCase())) { + target = el; + break; + } } + if (!target) return { error: `No clickable element found with text: ${text}` }; + } else if (selector) { + target = await page.$(selector); + if (!target) return { error: `Element not found: ${selector}` }; + } else { + return { error: 'Either selector or text required' }; } - if (!target) return { error: `No clickable element found with text: ${text}` }; - } else if (selector) { - target = await page.$(selector); - if (!target) return { error: `Element not found: ${selector}` }; - } else { - return { error: 'Either selector or text required' }; - } - const point = await this._pointForElement(target); - if (!point) return { error: 'Element has no visible clickable area' }; + const point = await this._pointForElement(target); + if (!point) return { error: 'Element has no visible clickable area' }; - await this._moveMouseTo(page, point.x, point.y); - await sleep(rand(170, 320)); - await page.mouse.down(); - await sleep(rand(80, 260)); - await page.mouse.up(); + await this._moveMouseTo(page, point.x, point.y, { signal: options.signal }); + await sleep(rand(170, 320), options.signal); + await page.mouse.down(); + await sleep(rand(80, 260), options.signal); + await page.mouse.up(); - await sleep(rand(800, 1800)); + await sleep(rand(800, 1800), options.signal); - let screenshotResult = null; - if (screenshot) screenshotResult = await this.takeScreenshot(); + let screenshotResult = null; + if (screenshot) screenshotResult = await this.takeScreenshot({ signal: options.signal }); - return { - success: true, - url: page.url(), - title: await page.title(), - screenshotPath: screenshotResult?.screenshotPath || null, - artifactId: screenshotResult?.artifactId || null, - fullPath: screenshotResult?.fullPath || null - }; + return { + success: true, + url: page.url(), + title: await page.title(), + screenshotPath: screenshotResult?.screenshotPath || null, + artifactId: screenshotResult?.artifactId || null, + fullPath: screenshotResult?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } - async clickPoint(x, y, screenshot = true) { - const page = await this.ensurePage(); + async clickPoint(x, y, screenshot = true, options = {}) { + const page = await this.ensurePage(options); try { - const px = Math.max(0, Math.round(Number(x) || 0)); - const py = Math.max(0, Math.round(Number(y) || 0)); - await this._moveMouseTo(page, px, py); - await sleep(rand(90, 220)); - await page.mouse.down(); - await sleep(rand(70, 240)); - await page.mouse.up(); - await sleep(rand(500, 1200)); - - let screenshotResult = null; - if (screenshot) screenshotResult = await this.takeScreenshot(); - - return { - success: true, - x: px, - y: py, - url: page.url(), - title: await page.title(), - screenshotPath: screenshotResult?.screenshotPath || null, - artifactId: screenshotResult?.artifactId || null, - fullPath: screenshotResult?.fullPath || null - }; + return await this._withPageCancellation(page, options.signal, async () => { + const px = Math.max(0, normalizePointCoordinate(x, 'x')); + const py = Math.max(0, normalizePointCoordinate(y, 'y')); + await this._moveMouseTo(page, px, py, { signal: options.signal }); + await sleep(rand(90, 220), options.signal); + await page.mouse.down(); + await sleep(rand(70, 240), options.signal); + await page.mouse.up(); + await sleep(rand(500, 1200), options.signal); + + let screenshotResult = null; + if (screenshot) screenshotResult = await this.takeScreenshot({ signal: options.signal }); + + return { + success: true, + x: px, + y: py, + url: page.url(), + title: await page.title(), + screenshotPath: screenshotResult?.screenshotPath || null, + artifactId: screenshotResult?.artifactId || null, + fullPath: screenshotResult?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } async hoverPoint(x, y, options = {}) { - const page = await this.ensurePage(); + const page = await this.ensurePage(options); try { - const px = Math.max(0, Math.round(Number(x) || 0)); - const py = Math.max(0, Math.round(Number(y) || 0)); - await this._moveMouseTo(page, px, py, { fast: Number(options.steps) <= 1 }); - return { - success: true, - x: px, - y: py, - url: page.url(), - title: await page.title() - }; + return await this._withPageCancellation(page, options.signal, async () => { + const px = Math.max(0, normalizePointCoordinate(x, 'x')); + const py = Math.max(0, normalizePointCoordinate(y, 'y')); + await this._moveMouseTo(page, px, py, { + fast: Number(options.steps) <= 1, + signal: options.signal, + }); + return { + success: true, + x: px, + y: py, + url: page.url(), + title: await page.title(), + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } - async scroll(deltaX = 0, deltaY = 0, screenshot = true) { - const page = await this.ensurePage(); + async scroll(deltaX = 0, deltaY = 0, screenshot = true, options = {}) { + const page = await this.ensurePage(options); try { - const x = Math.max(10, Math.min(this._viewport.width - 10, this._mousePosition.x + rand(-80, 80))); - const y = Math.max(10, Math.min(this._viewport.height - 10, this._mousePosition.y + rand(-80, 80))); - await this._moveMouseTo(page, x, y); - const totalX = Math.round(Number(deltaX) || 0); - const totalY = Math.round(Number(deltaY) || 0); - const chunks = Math.max(1, Math.min(6, Math.ceil(Math.max(Math.abs(totalX), Math.abs(totalY)) / 450))); - for (let i = 0; i < chunks; i += 1) { - await page.mouse.wheel({ - deltaX: Math.round(totalX / chunks), - deltaY: Math.round(totalY / chunks), - }); - await sleep(rand(120, 360)); - } + return await this._withPageCancellation(page, options.signal, async () => { + const x = Math.max(10, Math.min(this._viewport.width - 10, this._mousePosition.x + rand(-80, 80))); + const y = Math.max(10, Math.min(this._viewport.height - 10, this._mousePosition.y + rand(-80, 80))); + await this._moveMouseTo(page, x, y, { signal: options.signal }); + const totalX = Math.round(Number(deltaX) || 0); + const totalY = Math.round(Number(deltaY) || 0); + const chunks = Math.max(1, Math.min(6, Math.ceil(Math.max(Math.abs(totalX), Math.abs(totalY)) / 450))); + for (let i = 0; i < chunks; i += 1) { + await page.mouse.wheel({ + deltaX: Math.round(totalX / chunks), + deltaY: Math.round(totalY / chunks), + }); + await sleep(rand(120, 360), options.signal); + } - let screenshotResult = null; - if (screenshot) screenshotResult = await this.takeScreenshot(); + let screenshotResult = null; + if (screenshot) screenshotResult = await this.takeScreenshot({ signal: options.signal }); - return { - success: true, - url: page.url(), - title: await page.title(), - screenshotPath: screenshotResult?.screenshotPath || null, - artifactId: screenshotResult?.artifactId || null, - fullPath: screenshotResult?.fullPath || null - }; + return { + success: true, + url: page.url(), + title: await page.title(), + screenshotPath: screenshotResult?.screenshotPath || null, + artifactId: screenshotResult?.artifactId || null, + fullPath: screenshotResult?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } async type(selector, text, options = {}) { - const page = await this.ensurePage(); + const page = await this.ensurePage(options); try { - if (options.clear !== false) { + return await this._withPageCancellation(page, options.signal, async () => { + const value = String(text ?? ''); const element = await page.$(selector); - if (element) { - const point = await this._pointForElement(element); - if (point) { - await this._moveMouseTo(page, point.x, point.y); - await sleep(rand(120, 260)); + if (!element) return { error: `Element not found: ${selector}` }; + const point = await this._pointForElement(element); + if (point) { + await this._moveMouseTo(page, point.x, point.y, { signal: options.signal }); + await sleep(rand(120, 260), options.signal); + } + + const locator = typeof page.locator === 'function' ? page.locator(selector) : null; + if (options.clear !== false) { + if (locator && typeof locator.fill === 'function') { + await locator.fill(''); + } else { + await page.click(selector, { clickCount: 3, delay: rand(40, 120) }); + await page.keyboard.press('Backspace'); } } - await page.click(selector, { clickCount: 3, delay: rand(40, 120) }); - await page.keyboard.press('Backspace'); - } - for (const char of text) { - await page.type(selector, char, { delay: rand(45, 180) }); - } + if (locator && typeof locator.pressSequentially === 'function') { + await locator.pressSequentially(value, { delay: rand(45, 140) }); + } else { + await page.type(selector, value, { delay: rand(45, 140) }); + } - if (options.pressEnter) { - await page.keyboard.press('Enter'); - await sleep(1000); - } + if (options.pressEnter) { + if (locator && typeof locator.press === 'function') await locator.press('Enter'); + else await page.keyboard.press('Enter'); + await sleep(1000, options.signal); + } - let screenshotResult = null; - if (options.screenshot !== false) screenshotResult = await this.takeScreenshot(); + let screenshotResult = null; + if (options.screenshot !== false) { + screenshotResult = await this.takeScreenshot({ signal: options.signal }); + } - return { - success: true, - typed: text, - screenshotPath: screenshotResult?.screenshotPath || null, - artifactId: screenshotResult?.artifactId || null, - fullPath: screenshotResult?.fullPath || null - }; + return { + success: true, + typed: value, + screenshotPath: screenshotResult?.screenshotPath || null, + artifactId: screenshotResult?.artifactId || null, + fullPath: screenshotResult?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } async typeText(text, options = {}) { - const page = await this.ensurePage(); + const page = await this.ensurePage(options); try { - for (const char of String(text || '')) { - await page.keyboard.type(char, { delay: rand(45, 160) }); - } + return await this._withPageCancellation(page, options.signal, async () => { + const value = String(text ?? ''); + await page.keyboard.type(value, { delay: rand(45, 140) }); - if (options.pressEnter) { - await page.keyboard.press('Enter'); - await sleep(800); - } + if (options.pressEnter) { + await page.keyboard.press('Enter'); + await sleep(800, options.signal); + } - let screenshotResult = null; - if (options.screenshot !== false) screenshotResult = await this.takeScreenshot(); + let screenshotResult = null; + if (options.screenshot !== false) { + screenshotResult = await this.takeScreenshot({ signal: options.signal }); + } - return { - success: true, - typed: String(text || ''), - screenshotPath: screenshotResult?.screenshotPath || null, - artifactId: screenshotResult?.artifactId || null, - fullPath: screenshotResult?.fullPath || null - }; + return { + success: true, + typed: value, + screenshotPath: screenshotResult?.screenshotPath || null, + artifactId: screenshotResult?.artifactId || null, + fullPath: screenshotResult?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } - async pressKey(key, screenshot = true) { - const page = await this.ensurePage(); + async pressKey(key, screenshot = true, options = {}) { + const page = await this.ensurePage(options); try { - const normalized = String(key || '').trim(); - if (!normalized) { - return { error: 'key required' }; - } - await page.keyboard.press(normalized); - await sleep(rand(250, 700)); - - let screenshotResult = null; - if (screenshot) screenshotResult = await this.takeScreenshot(); - - return { - success: true, - key: normalized, - screenshotPath: screenshotResult?.screenshotPath || null, - artifactId: screenshotResult?.artifactId || null, - fullPath: screenshotResult?.fullPath || null - }; + return await this._withPageCancellation(page, options.signal, async () => { + const normalized = String(key || '').trim(); + if (!normalized) { + return { error: 'key required' }; + } + await page.keyboard.press(normalized); + await sleep(rand(250, 700), options.signal); + + let screenshotResult = null; + if (screenshot) screenshotResult = await this.takeScreenshot({ signal: options.signal }); + + return { + success: true, + key: normalized, + screenshotPath: screenshotResult?.screenshotPath || null, + artifactId: screenshotResult?.artifactId || null, + fullPath: screenshotResult?.fullPath || null, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } - async extract(selector, attribute, all = false) { - const page = await this.ensurePage(); + async extract(selector, attribute, all = false, options = {}) { + const page = await this.ensurePage(options); try { - const rawHtml = await page.content().catch(() => ''); - const botDetection = await this._getBotDetection(page, rawHtml, '').catch(() => ({ detected: false, provider: null })); - if (all) { - const results = await page.$$eval(selector || 'body', (elements, attr) => { - return elements.map(el => { - if (attr === 'innerHTML') return el.innerHTML; - if (attr === 'outerHTML') return el.outerHTML; - if (attr) return el.getAttribute(attr) || ''; - return el.innerText || ''; - }); - }, attribute); - return { results: results.slice(0, 100), botDetection }; - } + return await this._withPageCancellation(page, options.signal, async () => { + const rawHtml = await page.content().catch(() => ''); + const botDetection = await this._getBotDetection(page, rawHtml, '') + .catch(() => ({ detected: false, provider: null })); + if (all) { + const results = await page.$$eval(selector || 'body', (elements, attr) => { + return elements.slice(0, 100).map(el => { + let value = ''; + if (attr === 'innerHTML') value = el.innerHTML; + else if (attr === 'outerHTML') value = el.outerHTML; + else if (attr) value = el.getAttribute(attr) || ''; + else value = el.innerText || ''; + return String(value).slice(0, 50000); + }); + }, attribute); + return { results, botDetection }; + } - const result = await page.$eval(selector || 'body', (el, attr) => { - if (attr === 'innerHTML') return el.innerHTML; - if (attr === 'outerHTML') return el.outerHTML; - if (attr) return el.getAttribute(attr) || ''; - return el.innerText || ''; - }, attribute); + const result = await page.$eval(selector || 'body', (el, attr) => { + if (attr === 'innerHTML') return el.innerHTML; + if (attr === 'outerHTML') return el.outerHTML; + if (attr) return el.getAttribute(attr) || ''; + return el.innerText || ''; + }, attribute); - return { result: typeof result === 'string' ? result.slice(0, 50000) : result, botDetection }; + return { result: typeof result === 'string' ? result.slice(0, 50000) : result, botDetection }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } - async evaluate(script) { - const page = await this.ensurePage(); + async evaluate(script, options = {}) { + const source = String(script || ''); + if (!source) return { error: 'script required' }; + if (source.length > 10000) return { error: 'script exceeds maximum length (10000)' }; + const page = await this.ensurePage(options); try { - const result = await page.evaluate(buildIsolatedEvaluationExpression(script)); - return { result: typeof result === 'object' ? JSON.stringify(result) : String(result) }; + return await this._withPageCancellation(page, options.signal, async () => { + const result = await page.evaluate(buildIsolatedEvaluationExpression(source)); + const serialized = typeof result === 'object' && result !== null + ? JSON.stringify(result) + : String(result); + const output = serialized ?? 'undefined'; + const maxChars = 1024 * 1024; + return { + result: output.slice(0, maxChars), + truncated: output.length > maxChars, + }; + }); } catch (err) { + if (isAbortError(err, options.signal)) throw err; return { error: err.message }; } } @@ -963,14 +1261,12 @@ class BrowserController { } async launch(options = {}) { - void options; - await this.ensureBrowser(); + await this.ensureBrowser(options); return { success: true }; } isLaunched() { - if (this.context) return true; - return !!(this.browser && typeof this.browser.isConnected === 'function' && this.browser.isConnected()); + return this._contextIsOpen(); } getPageCount() { @@ -981,84 +1277,139 @@ class BrowserController { try { return this.browser.pages ? 1 : 0; } catch { return 0; } } - async fill(selector, value) { - return this.type(selector, String(value)); + async fill(selector, value, options = {}) { + return this.type(selector, String(value), options); } async extractContent(options = {}) { - return this.extract(options.selector, options.attribute, options.all); + return this.extract(options.selector, options.attribute, options.all, options); } - async executeJS(code) { - return this.evaluate(code); + async executeJS(code, options = {}) { + return this.evaluate(code, options); } - async getPageInfo() { + async getPageInfo(options = {}) { + throwIfAborted(options.signal); if (!this.page || this.page.isClosed()) return { url: null, title: null }; return { url: this.page.url(), - title: await this.page.title() + title: await raceWithSignal(this.page.title(), options.signal), }; } - async getCookies() { - await this.ensureBrowser(); + async getCookies(options = {}) { + await this.ensureBrowser(options); if (!this.context || typeof this.context.cookies !== 'function') { return { cookies: [] }; } - const cookies = await this.context.cookies().catch(() => []); + const cookies = await raceWithSignal(this.context.cookies(), options.signal); return { cookies: Array.isArray(cookies) ? cookies : [], }; } async close() { - if (this.page && !this.page.isClosed()) { - await this.page.close().catch(() => { }); - } - if (this.context) { - await this.context.close().catch(() => { }); - this.context = null; - this.browser = null; + if (this._closePromise) return this._closePromise; + this._closing = true; + this._launchAbortController?.abort(new Error('Browser controller is closing.')); + this._closePromise = (async () => { + const launchPromise = this.launchPromise; + if (launchPromise) await launchPromise.catch(() => {}); + + const page = this.page; + const context = this.context; + const browser = this.browser; this.page = null; - return; - } - if (this.browser) { - await this.browser.close().catch(() => { }); + this.context = null; this.browser = null; - this.page = null; + + if (context) { + await context.close({ reason: 'Browser controller closed' }).catch(() => {}); + } else if (browser) { + await browser.close().catch(() => {}); + } else if (page && !page.isClosed()) { + await page.close({ runBeforeUnload: false, reason: 'Browser controller closed' }).catch(() => {}); + } + await this._stopVirtualDisplay(); + })(); + try { + await this._closePromise; + } finally { + this._closePromise = null; + this._closing = false; } } - async ensureVirtualDisplay() { + async ensureVirtualDisplay(options = {}) { if (process.platform !== 'linux') { return; } - if (this.displayProcess && !this.displayProcess.killed) { + if (this.displayProcess && this.displayProcess.exitCode == null) { return; } if (this.displayValue && String(this.displayValue).trim()) { return; } - const display = ':99'; + const display = chooseVirtualDisplay(); const child = spawn('Xvfb', [display, '-screen', '0', '1440x900x24', '-ac', '-nolisten', 'tcp'], { stdio: ['ignore', 'ignore', 'pipe'], }); + this.displayProcess = child; let launchError = ''; child.stderr.on('data', (chunk) => { - launchError += chunk.toString(); + if (launchError.length < 64 * 1024) launchError += chunk.toString(); + }); + child.once('close', () => { + if (this.displayProcess === child) { + this.displayProcess = null; + if (this.displayValue === display) this.displayValue = process.env.DISPLAY || null; + if (this._managedDisplayValue === display) this._managedDisplayValue = null; + } }); - await sleep(1000); - if (child.exitCode != null) { - throw new Error(`Failed to start Xvfb: ${String(launchError || `exit code ${child.exitCode}`).trim()}`); + try { + await Promise.race([ + sleep(1000, options.signal), + new Promise((_, reject) => child.once('error', reject)), + ]); + if (child.exitCode != null) { + throw new Error(`Failed to start Xvfb: ${String(launchError || `exit code ${child.exitCode}`).trim()}`); + } + } catch (error) { + await this._stopVirtualDisplay(); + throw error; } - this.displayProcess = child; this.displayValue = display; + this._managedDisplayValue = display; + } + + async _stopVirtualDisplay() { + const child = this.displayProcess; + const managedDisplay = this._managedDisplayValue; + this.displayProcess = null; + this._managedDisplayValue = null; + if (managedDisplay && this.displayValue === managedDisplay) { + this.displayValue = process.env.DISPLAY || null; + } + if (!child || child.exitCode != null) return; + + const exited = new Promise((resolve) => child.once('close', resolve)); + try { child.kill('SIGTERM'); } catch {} + await Promise.race([exited, sleep(1000)]); + if (child.exitCode == null) { + try { child.kill('SIGKILL'); } catch {} + await Promise.race([exited, sleep(1000)]); + } } } -module.exports = { BrowserController, resolveBrowserExecutablePath, buildIsolatedEvaluationExpression, normalizeWaitUntil }; +module.exports = { + BrowserController, + buildIsolatedEvaluationExpression, + normalizeWaitUntil, + resolveBrowserExecutablePath, +}; diff --git a/server/services/browser/extension/gateway.js b/server/services/browser/extension/gateway.js index 8a389c4f..99235bc3 100644 --- a/server/services/browser/extension/gateway.js +++ b/server/services/browser/extension/gateway.js @@ -3,6 +3,7 @@ const { BROWSER_EXTENSION_WS_PATH } = require('./protocol'); const DEFAULT_UPGRADE_RATE_LIMIT_WINDOW_MS = 60 * 1000; const DEFAULT_UPGRADE_RATE_LIMIT_MAX = 30; +const MAX_EXTENSION_MESSAGE_BYTES = 32 * 1024 * 1024; function rejectUpgrade(socket, statusCode, message) { try { @@ -22,8 +23,13 @@ function rejectUpgrade(socket, statusCode, message) { } function bindBrowserExtensionGateway(httpServer, app) { - const wss = new WebSocketServer({ noServer: true }); + const wss = new WebSocketServer({ + noServer: true, + maxPayload: MAX_EXTENSION_MESSAGE_BYTES, + }); const attemptsByIp = new Map(); + let closing = false; + let closePromise = null; const windowMs = Number(process.env.NEOAGENT_BROWSER_EXTENSION_UPGRADE_WINDOW_MS || DEFAULT_UPGRADE_RATE_LIMIT_WINDOW_MS); const cleanupTimer = setInterval(() => { @@ -50,7 +56,7 @@ function bindBrowserExtensionGateway(httpServer, app) { return entry.count > maxAttempts; } - httpServer.on('upgrade', (req, socket, head) => { + const handleUpgrade = (req, socket, head) => { let url; try { url = new URL(req.url, 'http://localhost'); @@ -60,6 +66,10 @@ function bindBrowserExtensionGateway(httpServer, app) { if (url.pathname !== BROWSER_EXTENSION_WS_PATH) { return; } + if (closing) { + rejectUpgrade(socket, 503, 'Service Unavailable'); + return; + } const remoteAddress = req.socket?.remoteAddress || 'unknown'; if (isRateLimited(remoteAddress)) { @@ -81,24 +91,38 @@ function bindBrowserExtensionGateway(httpServer, app) { } wss.handleUpgrade(req, socket, head, (ws) => { - registry.registerConnection(tokenRow, ws, { - remoteAddress, - userAgent: req.headers['user-agent'] || null, - }); - ws.send(JSON.stringify({ - type: 'hello', - ok: true, - userId: tokenRow.user_id, - tokenId: tokenRow.id, - })); + try { + registry.registerConnection(tokenRow, ws, { + remoteAddress, + userAgent: req.headers['user-agent'] || null, + }); + ws.send(JSON.stringify({ + type: 'hello', + ok: true, + userId: tokenRow.user_id, + tokenId: tokenRow.id, + })); + } catch (error) { + try { ws.close(1012, String(error?.message || 'Service unavailable').slice(0, 120)); } catch {} + } }); - }); + }; + httpServer.on('upgrade', handleUpgrade); app.locals.browserExtensionGateway = { - close: () => new Promise((resolve) => { + close: () => { + if (closePromise) return closePromise; + closing = true; clearInterval(cleanupTimer); - wss.close(() => resolve()); - }), + httpServer.removeListener('upgrade', handleUpgrade); + for (const client of wss.clients) { + try { client.terminate(); } catch {} + } + closePromise = new Promise((resolve) => { + wss.close(() => resolve()); + }); + return closePromise; + }, }; return wss; diff --git a/server/services/browser/extension/protocol.js b/server/services/browser/extension/protocol.js index cafd2c9d..1d4696f0 100644 --- a/server/services/browser/extension/protocol.js +++ b/server/services/browser/extension/protocol.js @@ -1,6 +1,15 @@ +'use strict'; + const BROWSER_EXTENSION_WS_PATH = '/api/browser-extension/ws'; const EXTENSION_PROTOCOL_VERSION = 1; +const EXTENSION_MESSAGE_TYPES = Object.freeze({ + COMMAND: 'command', + RESULT: 'result', + URL_VALIDATION_REQUEST: 'urlValidationRequest', + URL_VALIDATION_RESULT: 'urlValidationResult', +}); + const EXTENSION_COMMANDS = Object.freeze({ LAUNCH: 'launch', NAVIGATE: 'navigate', @@ -16,6 +25,7 @@ const EXTENSION_COMMANDS = Object.freeze({ CLOSE: 'close', GET_PAGE_INFO: 'getPageInfo', GET_COOKIES: 'getCookies', + CANCEL_COMMAND: 'cancelCommand', }); class ExtensionBrowserUnavailableError extends Error { @@ -28,7 +38,7 @@ class ExtensionBrowserUnavailableError extends Error { function createCommandMessage(id, command, payload = {}) { return { - type: 'command', + type: EXTENSION_MESSAGE_TYPES.COMMAND, version: EXTENSION_PROTOCOL_VERSION, id, command, @@ -46,6 +56,7 @@ function parseExtensionMessage(data) { module.exports = { BROWSER_EXTENSION_WS_PATH, EXTENSION_PROTOCOL_VERSION, + EXTENSION_MESSAGE_TYPES, EXTENSION_COMMANDS, ExtensionBrowserUnavailableError, createCommandMessage, diff --git a/server/services/browser/extension/provider.js b/server/services/browser/extension/provider.js index 523faaf0..9a8abe01 100644 --- a/server/services/browser/extension/provider.js +++ b/server/services/browser/extension/provider.js @@ -1,18 +1,15 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const { DATA_DIR } = require('../../../../runtime/paths'); +const { writeBufferAtomic } = require('../../../utils/files'); +const { decodeBase64Image } = require('../../../utils/image_payload'); const { EXTENSION_COMMANDS, ExtensionBrowserUnavailableError } = require('./protocol'); const SCREENSHOTS_DIR = path.join(DATA_DIR, 'screenshots'); if (!fs.existsSync(SCREENSHOTS_DIR)) fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); -function extractBase64Png(value) { - const text = String(value || ''); - if (!text) return null; - const match = text.match(/^data:image\/png;base64,(.+)$/); - return match ? match[1] : text; -} - class ExtensionBrowserProvider { constructor(options = {}) { this.registry = options.registry; @@ -31,11 +28,18 @@ class ExtensionBrowserProvider { async #dispatch(command, payload = {}, options = {}) { this.#assertReady(); - const result = await this.registry.dispatch(this.userId, command, payload, { + const safePayload = { ...(payload || {}) }; + const signal = options.signal || safePayload.signal || null; + const timeoutMs = options.timeoutMs ?? safePayload.timeoutMs; + delete safePayload.signal; + delete safePayload.timeoutMs; + const result = await this.registry.dispatch(this.userId, command, safePayload, { ...options, + signal, + timeoutMs, tokenId: options.tokenId || this.tokenId, }); - return this.#materialize(result); + return this.#materialize(result, { signal }); } #disconnect() { @@ -46,18 +50,17 @@ class ExtensionBrowserProvider { } } - #writeScreenshotArtifact(base64) { - const buffer = Buffer.from(base64, 'base64'); + async #writeScreenshotArtifact(image, options = {}) { if (this.artifactStore && this.userId != null) { - const artifact = this.artifactStore.allocateFile(this.userId, { + const artifact = await this.artifactStore.createBufferArtifact(this.userId, { kind: 'browser-screenshot', backend: 'extension', - extension: 'png', - contentType: 'image/png', + extension: image.extension, + contentType: image.contentType, filenameBase: 'browser-extension-screenshot', + content: image.buffer, + signal: options.signal, }); - fs.writeFileSync(artifact.storagePath, buffer); - this.artifactStore.finalizeFile(artifact.artifactId, artifact.storagePath); return { screenshotPath: artifact.url, artifactId: artifact.artifactId, @@ -68,7 +71,7 @@ class ExtensionBrowserProvider { const filename = `browser_extension_${Date.now()}_${Math.random().toString(16).slice(2)}.png`; const fullPath = path.join(SCREENSHOTS_DIR, filename); - fs.writeFileSync(fullPath, buffer); + await writeBufferAtomic(fullPath, image.buffer, { signal: options.signal }); return { screenshotPath: `/screenshots/${filename}`, artifactId: null, @@ -77,13 +80,12 @@ class ExtensionBrowserProvider { }; } - #materialize(result) { + async #materialize(result, options = {}) { if (!result || typeof result !== 'object') return result; const raw = result.screenshotDataUrl || result.screenshotData || result.screenshotBase64; if (!raw) return result; - const base64 = extractBase64Png(raw); - if (!base64) return result; - const screenshot = this.#writeScreenshotArtifact(base64); + const image = decodeBase64Image(raw, { allowedTypes: ['image/png'] }); + const screenshot = await this.#writeScreenshotArtifact(image, options); const next = { ...result, ...screenshot }; delete next.screenshotDataUrl; delete next.screenshotData; @@ -92,79 +94,90 @@ class ExtensionBrowserProvider { } navigate(url, options = {}) { - return this.#dispatch(EXTENSION_COMMANDS.NAVIGATE, { url, ...options }); + return this.#dispatch(EXTENSION_COMMANDS.NAVIGATE, { url, ...options }, options); } - click(selector, text, screenshot = true) { - return this.#dispatch(EXTENSION_COMMANDS.CLICK, { selector, text, screenshot }); + click(selector, text, screenshot = true, options = {}) { + return this.#dispatch(EXTENSION_COMMANDS.CLICK, { selector, text, screenshot }, options); } - clickPoint(x, y, screenshot = true) { - return this.#dispatch(EXTENSION_COMMANDS.CLICK_POINT, { x, y, screenshot }); + clickPoint(x, y, screenshot = true, options = {}) { + return this.#dispatch(EXTENSION_COMMANDS.CLICK_POINT, { x, y, screenshot }, options); } type(selector, text, options = {}) { - return this.#dispatch(EXTENSION_COMMANDS.TYPE, { selector, text, ...options }); + return this.#dispatch(EXTENSION_COMMANDS.TYPE, { selector, text, ...options }, options); } typeText(text, options = {}) { - return this.#dispatch(EXTENSION_COMMANDS.TYPE_TEXT, { text, ...options }); + return this.#dispatch(EXTENSION_COMMANDS.TYPE_TEXT, { text, ...options }, options); } - pressKey(key, screenshot = true) { - return this.#dispatch(EXTENSION_COMMANDS.PRESS_KEY, { key, screenshot }); + pressKey(key, screenshot = true, options = {}) { + return this.#dispatch(EXTENSION_COMMANDS.PRESS_KEY, { key, screenshot }, options); } - scroll(deltaX = 0, deltaY = 0, screenshot = true) { - return this.#dispatch(EXTENSION_COMMANDS.SCROLL, { deltaX, deltaY, screenshot }); + scroll(deltaX = 0, deltaY = 0, screenshot = true, options = {}) { + return this.#dispatch(EXTENSION_COMMANDS.SCROLL, { deltaX, deltaY, screenshot }, options); } - extract(selector, attribute, all = false) { - return this.#dispatch(EXTENSION_COMMANDS.EXTRACT, { selector, attribute, all }); + extract(selector, attribute, all = false, options = {}) { + return this.#dispatch(EXTENSION_COMMANDS.EXTRACT, { selector, attribute, all }, options); } - evaluate(script) { - return this.#dispatch(EXTENSION_COMMANDS.EVALUATE, { script }); + evaluate(script, options = {}) { + return this.#dispatch(EXTENSION_COMMANDS.EVALUATE, { script }, options); } screenshot(options = {}) { - return this.#dispatch(EXTENSION_COMMANDS.SCREENSHOT, options); + return this.#dispatch(EXTENSION_COMMANDS.SCREENSHOT, options, options); } launch(options = {}) { - return this.#dispatch(EXTENSION_COMMANDS.LAUNCH, options); + return this.#dispatch(EXTENSION_COMMANDS.LAUNCH, options, options); } - async closeBrowser() { + async closeBrowser(options = {}) { if (!this.registry || this.userId == null || !this.registry.isConnected(this.userId, this.tokenId)) { return { success: true, extensionConnected: false }; } - const result = await this.#dispatch(EXTENSION_COMMANDS.CLOSE, {}); + const result = await this.#dispatch(EXTENSION_COMMANDS.CLOSE, {}, options); this.#disconnect(); return { ...result, success: result?.success !== false, extensionConnected: false }; } - fill(selector, value) { - return this.type(selector, String(value)); + fill(selector, value, options = {}) { + return this.type(selector, String(value), options); } extractContent(options = {}) { - return this.extract(options.selector, options.attribute, options.all); + return this.extract(options.selector, options.attribute, options.all, options); } - executeJS(code) { - return this.evaluate(code); + executeJS(code, options = {}) { + return this.evaluate(code, options); } - async getPageInfo() { + async getPageInfo(options = {}) { if (!this.registry || this.userId == null || !this.registry.isConnected(this.userId, this.tokenId)) { return { url: null, title: null, extensionConnected: false }; } return this.registry.dispatch(this.userId, EXTENSION_COMMANDS.GET_PAGE_INFO, {}, { tokenId: this.tokenId, + signal: options.signal, }); } + async getCookies(options = {}) { + const pageInfo = await this.getPageInfo(options); + let hostname = ''; + try { hostname = new URL(String(pageInfo?.url || '')).hostname; } catch {} + if (!hostname) return { cookies: [], domains: [] }; + return this.#dispatch(EXTENSION_COMMANDS.GET_COOKIES, { + domains: [hostname], + }, options); + } + isLaunched() { return Boolean(this.registry && this.userId != null && this.registry.isConnected(this.userId, this.tokenId)); } @@ -181,5 +194,4 @@ class ExtensionBrowserProvider { module.exports = { ExtensionBrowserProvider, - extractBase64Png, }; diff --git a/server/services/browser/extension/registry.js b/server/services/browser/extension/registry.js index 6c0f8243..8c79d9a7 100644 --- a/server/services/browser/extension/registry.js +++ b/server/services/browser/extension/registry.js @@ -1,6 +1,13 @@ +'use strict'; + const crypto = require('crypto'); const db = require('../../../db/database'); +const { createAbortError } = require('../../../utils/abort'); +const { validateCloudUrlWithDns } = require('../../../utils/cloud-security'); const { + EXTENSION_COMMANDS, + EXTENSION_MESSAGE_TYPES, + EXTENSION_PROTOCOL_VERSION, ExtensionBrowserUnavailableError, createCommandMessage, parseExtensionMessage, @@ -11,6 +18,8 @@ const DEFAULT_COMMAND_TIMEOUT_MS = 30 * 1000; const DEFAULT_HEARTBEAT_INTERVAL_MS = 25 * 1000; const DEFAULT_HEARTBEAT_TIMEOUT_MS = 75 * 1000; const DEFAULT_PRESENCE_TOUCH_INTERVAL_MS = 15 * 1000; +const DEFAULT_URL_VALIDATION_LIMIT = 128; +const MAX_URL_VALIDATION_CHARS = 8192; function sha256(value) { return crypto.createHash('sha256').update(String(value || '')).digest('hex'); @@ -48,7 +57,13 @@ class BrowserExtensionRegistry { this.heartbeatIntervalMs = Number(options.heartbeatIntervalMs || process.env.NEOAGENT_BROWSER_EXTENSION_HEARTBEAT_INTERVAL_MS || DEFAULT_HEARTBEAT_INTERVAL_MS); this.heartbeatTimeoutMs = Number(options.heartbeatTimeoutMs || process.env.NEOAGENT_BROWSER_EXTENSION_HEARTBEAT_TIMEOUT_MS || DEFAULT_HEARTBEAT_TIMEOUT_MS); this.presenceTouchIntervalMs = Number(options.presenceTouchIntervalMs || process.env.NEOAGENT_BROWSER_EXTENSION_PRESENCE_TOUCH_INTERVAL_MS || DEFAULT_PRESENCE_TOUCH_INTERVAL_MS); + this.urlValidator = options.urlValidator || validateCloudUrlWithDns; + this.urlValidationLimit = Math.max(1, Math.min( + 512, + Number(options.urlValidationLimit || DEFAULT_URL_VALIDATION_LIMIT), + )); this.connectionsByUser = new Map(); + this.closed = false; } #getUserConnections(userId, create = false) { @@ -204,6 +219,9 @@ class BrowserExtensionRegistry { } registerConnection(tokenRow, ws, meta = {}) { + if (this.closed) { + throw new ExtensionBrowserUnavailableError('Browser extension registry is shutting down.'); + } const userId = String(tokenRow.user_id); const userMap = this.#getUserConnections(userId, true); const existing = userMap.get(tokenRow.id); @@ -223,11 +241,15 @@ class BrowserExtensionRegistry { presenceTouchIntervalMs: this.presenceTouchIntervalMs, }); userMap.set(tokenRow.id, connection); - this.db.prepare( - `UPDATE browser_extension_tokens - SET last_connected_at = datetime('now'), last_seen_at = datetime('now') - WHERE id = ?` - ).run(tokenRow.id); + try { + this.db.prepare( + `UPDATE browser_extension_tokens + SET last_connected_at = datetime('now'), last_seen_at = datetime('now') + WHERE id = ?` + ).run(tokenRow.id); + } catch (error) { + console.warn('[BrowserExtension] Failed to record connection presence:', error?.message); + } return connection; } @@ -322,9 +344,13 @@ class BrowserExtensionRegistry { const userMap = this.#getUserConnections(userId); const connection = userMap?.get(String(tokenId)); if (!connection?.isOpen()) return; - this.db.prepare( - `UPDATE browser_extension_tokens SET last_seen_at = datetime('now') WHERE id = ?` - ).run(tokenId); + try { + this.db.prepare( + `UPDATE browser_extension_tokens SET last_seen_at = datetime('now') WHERE id = ?` + ).run(tokenId); + } catch (error) { + console.warn('[BrowserExtension] Failed to update connection presence:', error?.message); + } } async dispatch(userId, command, payload = {}, options = {}) { @@ -333,12 +359,14 @@ class BrowserExtensionRegistry { throw new ExtensionBrowserUnavailableError(); } const result = await connection.sendCommand(command, payload, options); - this.db.prepare( - `UPDATE browser_extension_tokens SET last_seen_at = datetime('now') WHERE id = ?` - ).run(connection.tokenId); + this.touchPresence(userId, connection.tokenId); return result; } + validateBrowserUrl(url, options = {}) { + return this.urlValidator(url, options); + } + getStatus(userId) { const userMap = this.#getUserConnections(userId); let selectedTokenId = this.getSelectedTokenId(userId); @@ -396,22 +424,22 @@ class BrowserExtensionRegistry { ).run(userId); } - const connection = this.getConnection(userId, targetTokenId); - if (connection && (!targetTokenId || connection.tokenId === targetTokenId)) { + const userMap = this.#getUserConnections(userId); + const connections = targetTokenId + ? [userMap?.get(targetTokenId)].filter(Boolean) + : Array.from(userMap?.values() || []); + for (const connection of connections) { connection.close('extension token revoked'); } return { success: true }; } closeAll() { - for (const connection of this.connectionsByUser.values()) { - if (connection instanceof Map) { - for (const nested of connection.values()) { - nested.close('server shutdown'); - } - } else { - connection.close('server shutdown'); - } + this.closed = true; + const connections = Array.from(this.connectionsByUser.values()) + .flatMap((entry) => entry instanceof Map ? Array.from(entry.values()) : [entry]); + for (const connection of connections) { + connection.close('server shutdown'); } this.connectionsByUser.clear(); } @@ -433,24 +461,34 @@ class ExtensionBrowserConnection { this.lastPongAt = Date.now(); this.lastPresenceTouchAt = 0; this.heartbeatTimer = null; + this.urlValidationCount = 0; + this.urlValidationController = new AbortController(); + this.closed = false; - ws.on('message', (data) => this.#handleMessage(data)); + ws.on('message', (data) => { + this.#handleMessage(data).catch(() => {}); + }); ws.on('pong', () => { this.lastPongAt = Date.now(); this.touchPresence(); }); ws.on('close', () => this.#closePending(new ExtensionBrowserUnavailableError('Extension browser disconnected.'))); - ws.on('error', (error) => this.#closePending(error)); + ws.on('error', (error) => { + try { ws.terminate(); } catch {} + this.#closePending(error); + }); this.#startHeartbeat(); } isOpen() { - return this.ws && this.ws.readyState === 1; + return !this.closed && this.ws && this.ws.readyState === 1; } close(reason) { + const wasOpen = this.isOpen(); + this.closed = true; try { - if (this.isOpen()) { + if (wasOpen) { this.ws.close(1000, String(reason || 'closing').slice(0, 120)); } } catch {} @@ -478,25 +516,57 @@ class ExtensionBrowserConnection { return Promise.reject(new ExtensionBrowserUnavailableError()); } const id = crypto.randomUUID(); - const timeoutMs = Number(options.timeoutMs || this.timeoutMs); + const requestedTimeout = Number(options.timeoutMs || this.timeoutMs); + const timeoutMs = Number.isFinite(requestedTimeout) + ? Math.min(10 * 60 * 1000, Math.max(100, requestedTimeout)) + : DEFAULT_COMMAND_TIMEOUT_MS; const message = createCommandMessage(id, command, payload); return new Promise((resolve, reject) => { - const timer = setTimeout(() => { + const signal = options.signal || null; + const sendCancellation = () => { + if (!this.isOpen() || command === EXTENSION_COMMANDS.CANCEL_COMMAND) return; + try { + this.ws.send(JSON.stringify(createCommandMessage( + crypto.randomUUID(), + EXTENSION_COMMANDS.CANCEL_COMMAND, + { commandId: id }, + ))); + } catch {} + }; + const cleanup = () => { + clearTimeout(timer); + signal?.removeEventListener('abort', onAbort); this.pending.delete(id); - reject(new Error(`Browser extension command timed out: ${command}`)); + }; + const onAbort = () => { + cleanup(); + sendCancellation(); + reject(createAbortError(signal, `Browser extension command aborted: ${command}`)); + }; + const timer = setTimeout(() => { + cleanup(); + sendCancellation(); + const error = new Error(`Browser extension command timed out: ${command}`); + error.code = 'BROWSER_EXTENSION_COMMAND_TIMEOUT'; + reject(error); }, timeoutMs); - this.pending.set(id, { resolve, reject, timer, command }); + timer.unref?.(); + if (signal?.aborted) { + onAbort(); + return; + } + signal?.addEventListener('abort', onAbort, { once: true }); + this.pending.set(id, { resolve, reject, timer, command, signal, onAbort }); try { this.ws.send(JSON.stringify(message)); } catch (error) { - clearTimeout(timer); - this.pending.delete(id); + cleanup(); reject(error); } }); } - #handleMessage(data) { + async #handleMessage(data) { this.lastPongAt = Date.now(); this.touchPresence(); let message; @@ -505,21 +575,70 @@ class ExtensionBrowserConnection { } catch { return; } - if (!message || message.type !== 'result' || !message.id) { + if (!message || !message.id) { return; } + if (message.type === EXTENSION_MESSAGE_TYPES.URL_VALIDATION_REQUEST) { + await this.#handleUrlValidationRequest(message); + return; + } + if (message.type !== EXTENSION_MESSAGE_TYPES.RESULT) return; const pending = this.pending.get(message.id); if (!pending) return; clearTimeout(pending.timer); + pending.signal?.removeEventListener('abort', pending.onAbort); this.pending.delete(message.id); if (message.ok === false) { pending.reject(new Error(message.error || `Browser extension command failed: ${pending.command}`)); return; } - pending.resolve(message.result || {}); + pending.resolve(message.result ?? {}); + } + + async #handleUrlValidationRequest(message) { + const id = typeof message.id === 'string' ? message.id : ''; + if (!id || id.length > 200 || !this.isOpen()) return; + + let allowed = false; + const url = typeof message.url === 'string' ? message.url : ''; + const versionMatches = Number(message.version) === EXTENSION_PROTOCOL_VERSION; + const validationLimit = Number(this.registry.urlValidationLimit) || DEFAULT_URL_VALIDATION_LIMIT; + if ( + versionMatches + && url.length > 0 + && url.length <= MAX_URL_VALIDATION_CHARS + && this.urlValidationCount < validationLimit + && typeof this.registry.validateBrowserUrl === 'function' + ) { + this.urlValidationCount += 1; + try { + const result = await this.registry.validateBrowserUrl(url, { + signal: this.urlValidationController.signal, + }); + allowed = result?.allowed === true; + } catch { + allowed = false; + } finally { + this.urlValidationCount -= 1; + } + } + + if (!this.isOpen()) return; + try { + this.ws.send(JSON.stringify({ + type: EXTENSION_MESSAGE_TYPES.URL_VALIDATION_RESULT, + version: EXTENSION_PROTOCOL_VERSION, + id, + allowed, + })); + } catch {} } #closePending(error) { + this.closed = true; + if (!this.urlValidationController.signal.aborted) { + this.urlValidationController.abort(error); + } this.registry.unregisterConnection(this); if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); @@ -527,6 +646,7 @@ class ExtensionBrowserConnection { } for (const pending of this.pending.values()) { clearTimeout(pending.timer); + pending.signal?.removeEventListener('abort', pending.onAbort); pending.reject(error); } this.pending.clear(); @@ -559,6 +679,7 @@ class ExtensionBrowserConnection { } module.exports = { + ExtensionBrowserConnection, BrowserExtensionRegistry, sha256, }; diff --git a/server/services/cli/executor.js b/server/services/cli/executor.js index a93068f9..fad3adc8 100644 --- a/server/services/cli/executor.js +++ b/server/services/cli/executor.js @@ -7,6 +7,22 @@ const FORCE_KILL_GRACE_MS = 5000; const MAX_STDOUT_CHARS = 50000; const MAX_STDERR_CHARS = 10000; +function abortedResult(command, cwd, startedAt = Date.now()) { + return { + exitCode: null, + stdout: '', + stderr: 'Command aborted before it started.', + killed: true, + timedOut: false, + aborted: true, + signal: null, + durationMs: Date.now() - startedAt, + pid: null, + command, + cwd, + }; +} + function resolveDefaultShell() { const candidates = [ process.env.SHELL, @@ -63,6 +79,22 @@ function terminateProcess(proc, signal = 'SIGTERM') { proc.kill?.(signal) || proc.kill?.(); } +function terminateWithEscalation(proc, isActive) { + terminateProcess(proc, 'SIGTERM'); + if (proc.__neoagentForceKillTimer) return; + proc.__neoagentForceKillTimer = setTimeout(() => { + if (isActive()) terminateProcess(proc, 'SIGKILL'); + }, FORCE_KILL_GRACE_MS); + proc.__neoagentForceKillTimer.unref?.(); +} + +function clearForceKill(proc) { + if (proc?.__neoagentForceKillTimer) { + clearTimeout(proc.__neoagentForceKillTimer); + proc.__neoagentForceKillTimer = null; + } +} + function shellSupportsPipefail(shellPath) { const normalized = String(shellPath || '').trim().toLowerCase(); return /(?:^|\/)(?:bash|zsh|ksh|mksh|yash)$/.test(normalized); @@ -111,6 +143,7 @@ class CLIExecutor { const cwd = options.cwd || process.env.HOME; const timeout = clampTimeout(options.timeout, DEFAULT_TIMEOUT_MS); const stdinInput = options.stdinInput; + if (options.signal?.aborted) return abortedResult(command, cwd); return new Promise((resolve) => { let stdout = ''; @@ -131,6 +164,13 @@ class CLIExecutor { const pid = proc.pid; this.activeProcesses.set(pid, proc); options.onSpawn?.(pid); + const onAbort = () => { + killed = true; + proc.__neoagentKilled = true; + proc.__neoagentKillReason = 'aborted'; + terminateWithEscalation(proc, () => this.activeProcesses.get(pid) === proc); + }; + options.signal?.addEventListener('abort', onAbort, { once: true }); proc.stdout.on('data', (data) => { stdout += data.toString(); @@ -156,14 +196,14 @@ class CLIExecutor { timedOut = true; proc.__neoagentKilled = true; proc.__neoagentKillReason = 'timeout'; - terminateProcess(proc, 'SIGTERM'); - setTimeout(() => { - if (!proc.killed) terminateProcess(proc, 'SIGKILL'); - }, FORCE_KILL_GRACE_MS); + terminateWithEscalation(proc, () => this.activeProcesses.get(pid) === proc); }, timeout); + timer.unref?.(); proc.on('close', (code, signal) => { clearTimeout(timer); + clearForceKill(proc); + options.signal?.removeEventListener('abort', onAbort); this.activeProcesses.delete(pid); const durationMs = Date.now() - startedAt; @@ -173,6 +213,7 @@ class CLIExecutor { stderr: truncateOutput(stderr.trim(), MAX_STDERR_CHARS), killed: killed || proc.__neoagentKilled === true, timedOut: timedOut || proc.__neoagentKillReason === 'timeout', + aborted: proc.__neoagentKillReason === 'aborted', signal: signal || null, durationMs, pid, @@ -183,6 +224,8 @@ class CLIExecutor { proc.on('error', (err) => { clearTimeout(timer); + clearForceKill(proc); + options.signal?.removeEventListener('abort', onAbort); this.activeProcesses.delete(pid); resolve({ exitCode: -1, @@ -204,6 +247,7 @@ class CLIExecutor { async executeInteractive(command, inputs = [], options = {}) { const cwd = options.cwd || process.env.HOME; const timeout = clampTimeout(options.timeout, DEFAULT_INTERACTIVE_TIMEOUT_MS); + if (options.signal?.aborted) return abortedResult(command, cwd); return new Promise((resolve) => { let output = ''; @@ -231,6 +275,13 @@ class CLIExecutor { const pid = proc.pid; this.activeProcesses.set(pid, proc); options.onSpawn?.(pid); + const onAbort = () => { + killed = true; + proc.__neoagentKilled = true; + proc.__neoagentKillReason = 'aborted'; + terminateWithEscalation(proc, () => this.activeProcesses.get(pid) === proc); + }; + options.signal?.addEventListener('abort', onAbort, { once: true }); proc.onData((data) => { output += data; @@ -256,11 +307,14 @@ class CLIExecutor { timedOut = true; proc.__neoagentKilled = true; proc.__neoagentKillReason = 'timeout'; - proc.kill(); + terminateWithEscalation(proc, () => this.activeProcesses.get(pid) === proc); }, timeout); + timer.unref?.(); proc.onExit(({ exitCode, signal }) => { clearTimeout(timer); + clearForceKill(proc); + options.signal?.removeEventListener('abort', onAbort); this.activeProcesses.delete(pid); const cleanOutput = output.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '').trim(); @@ -270,6 +324,7 @@ class CLIExecutor { stderr: '', killed: killed || proc.__neoagentKilled === true, timedOut: timedOut || proc.__neoagentKillReason === 'timeout', + aborted: proc.__neoagentKillReason === 'aborted', signal: typeof signal === 'number' ? String(signal) : signal || null, durationMs: Date.now() - startedAt, pid, @@ -286,8 +341,7 @@ class CLIExecutor { if (proc) { proc.__neoagentKilled = true; proc.__neoagentKillReason = reason; - terminateProcess(proc, 'SIGTERM'); - this.activeProcesses.delete(pid); + terminateWithEscalation(proc, () => this.activeProcesses.get(pid) === proc); return true; } return false; @@ -301,9 +355,8 @@ class CLIExecutor { for (const [pid, proc] of this.activeProcesses) { proc.__neoagentKilled = true; proc.__neoagentKillReason = reason; - terminateProcess(proc, 'SIGTERM'); + terminateWithEscalation(proc, () => this.activeProcesses.get(pid) === proc); } - this.activeProcesses.clear(); } } diff --git a/server/services/desktop/gateway.js b/server/services/desktop/gateway.js index 5e0823f8..3505882a 100644 --- a/server/services/desktop/gateway.js +++ b/server/services/desktop/gateway.js @@ -1,3 +1,5 @@ +'use strict'; + const { WebSocketServer } = require('ws'); const { DESKTOP_COMPANION_WS_PATH, @@ -15,6 +17,9 @@ const { const UPGRADE_RATE_LIMIT_WINDOW_MS = 60 * 1000; const UPGRADE_RATE_LIMIT_MAX_ATTEMPTS = 30; const UPGRADE_RATE_LIMIT_ENTRY_TTL_MS = 10 * 60 * 1000; +const UPGRADE_AUTH_TIMEOUT_MS = 5000; +const HELLO_TIMEOUT_MS = 5000; +const MAX_HELLO_BYTES = 64 * 1024; function rejectUpgrade(socket, statusCode, message) { try { @@ -88,6 +93,8 @@ function bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamH }); const upgradeAttempts = new Map(); const upgradeThrottleObserver = createUpgradeThrottleObserver(); + let closing = false; + let closePromise = null; if (app?.locals) { app.locals.getDesktopGatewayRateLimitSnapshot = () => upgradeThrottleObserver.snapshot(); @@ -115,7 +122,7 @@ function bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamH return true; } - httpServer.on('upgrade', (req, socket, head) => { + const handleUpgrade = (req, socket, head) => { let url; try { url = new URL(req.url, 'http://localhost'); @@ -125,6 +132,10 @@ function bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamH if (url.pathname !== DESKTOP_COMPANION_WS_PATH) { return; } + if (closing) { + rejectUpgrade(socket, 503, 'Service Unavailable'); + return; + } const remoteAddress = remoteAddressFromRequest(req); if (!allowUpgradeAttempt(remoteAddress)) { @@ -133,7 +144,16 @@ function bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamH return; } + const authTimer = setTimeout(() => { + rejectUpgrade(socket, 504, 'Gateway Timeout'); + }, UPGRADE_AUTH_TIMEOUT_MS); + authTimer.unref?.(); sessionMiddleware(req, {}, (err) => { + clearTimeout(authTimer); + if (closing || socket.destroyed) { + if (!socket.destroyed) rejectUpgrade(socket, 503, 'Service Unavailable'); + return; + } if (err) { rejectUpgrade(socket, 500, 'Session Error'); return; @@ -155,11 +175,18 @@ function bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamH if (!initialized) { try { ws.close(1008, 'Desktop companion hello timed out'); } catch {} } - }, 5000); + }, HELLO_TIMEOUT_MS); + helloTimer.unref?.(); + const clearHelloTimer = () => clearTimeout(helloTimer); + ws.once('close', clearHelloTimer); + ws.once('error', clearHelloTimer); ws.once('message', (data) => { clearTimeout(helloTimer); try { + if (Buffer.byteLength(data) > MAX_HELLO_BYTES) { + throw Object.assign(new Error('Desktop companion hello is too large.'), { status: 413 }); + } const message = parseDesktopMessage(data); if (!isDesktopCompanionHello(message)) { throw Object.assign(new Error('Desktop companion hello is required.'), { status: 400 }); @@ -227,11 +254,21 @@ function bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamH }); }); }); - }); + }; + httpServer.on('upgrade', handleUpgrade); if (app?.locals) { app.locals.desktopCompanionGateway = { - close: () => new Promise((resolve) => wss.close(() => resolve())), + close: () => { + if (closePromise) return closePromise; + closing = true; + httpServer.removeListener('upgrade', handleUpgrade); + for (const client of wss.clients) { + try { client.terminate(); } catch {} + } + closePromise = new Promise((resolve) => wss.close(() => resolve())); + return closePromise; + }, }; } diff --git a/server/services/desktop/protocol.js b/server/services/desktop/protocol.js index b7bd34be..39a88931 100644 --- a/server/services/desktop/protocol.js +++ b/server/services/desktop/protocol.js @@ -1,3 +1,5 @@ +'use strict'; + const DESKTOP_COMPANION_WS_PATH = '/api/desktop/ws'; const DESKTOP_COMMANDS = Object.freeze({ @@ -17,6 +19,7 @@ const DESKTOP_COMMANDS = Object.freeze({ GET_TREE: 'getTree', PAUSE_CONTROL: 'pauseControl', EXECUTE_COMMAND: 'executeCommand', + CANCEL_COMMAND: 'cancelCommand', PING: 'ping', MOUSE_MOVE: 'mouseMove', }); diff --git a/server/services/desktop/provider.js b/server/services/desktop/provider.js index bbaeb93b..31cff241 100644 --- a/server/services/desktop/provider.js +++ b/server/services/desktop/provider.js @@ -1,28 +1,18 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const { DATA_DIR } = require('../../../runtime/paths'); +const { writeBufferAtomic } = require('../../utils/files'); +const { decodeBase64Image } = require('../../utils/image_payload'); const { DESKTOP_COMMANDS, - DesktopCompanionSelectionError, DesktopCompanionUnavailableError, } = require('./protocol'); const SCREENSHOTS_DIR = path.join(DATA_DIR, 'screenshots'); if (!fs.existsSync(SCREENSHOTS_DIR)) fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); -function extractBase64Image(value) { - const text = String(value || ''); - if (!text) return null; - const match = text.match(/^data:image\/(?:png|jpeg|jpg);base64,(.+)$/i); - return match ? match[1] : text; -} - -function guessExtension(result = {}) { - const mime = String(result.contentType || result.mimeType || 'image/png').toLowerCase(); - if (mime.includes('jpeg') || mime.includes('jpg')) return 'jpg'; - return 'png'; -} - class DesktopProvider { constructor(options = {}) { this.registry = options.registry; @@ -36,24 +26,21 @@ class DesktopProvider { } } - _writeScreenshotArtifact(base64, result = {}) { - const buffer = Buffer.from(base64, 'base64'); - const extension = guessExtension(result); - const contentType = result.contentType || (extension === 'jpg' ? 'image/jpeg' : 'image/png'); + async _writeScreenshotArtifact(image, result = {}, options = {}) { if (this.artifactStore && this.userId != null) { - const artifact = this.artifactStore.allocateFile(this.userId, { + const artifact = await this.artifactStore.createBufferArtifact(this.userId, { kind: 'desktop-screenshot', backend: 'desktop-companion', - extension, - contentType, + extension: image.extension, + contentType: image.contentType, filenameBase: 'desktop-companion-screenshot', + content: image.buffer, + signal: options.signal, metadata: { deviceId: result.device?.deviceId || null, displayId: result.displayId || result.device?.activeDisplayId || null, }, }); - fs.writeFileSync(artifact.storagePath, buffer); - this.artifactStore.finalizeFile(artifact.artifactId, artifact.storagePath); return { screenshotPath: artifact.url, artifactId: artifact.artifactId, @@ -62,9 +49,9 @@ class DesktopProvider { }; } - const filename = `desktop_${Date.now()}_${Math.random().toString(16).slice(2)}.${extension}`; + const filename = `desktop_${Date.now()}_${Math.random().toString(16).slice(2)}.${image.extension}`; const fullPath = path.join(SCREENSHOTS_DIR, filename); - fs.writeFileSync(fullPath, buffer); + await writeBufferAtomic(fullPath, image.buffer, { signal: options.signal }); return { screenshotPath: `/screenshots/${filename}`, artifactId: null, @@ -73,13 +60,14 @@ class DesktopProvider { }; } - _materialize(result) { + async _materialize(result, options = {}) { if (!result || typeof result !== 'object') return result; const raw = result.screenshotDataUrl || result.screenshotData || result.screenshotBase64; if (!raw) return result; - const base64 = extractBase64Image(raw); - if (!base64) return result; - const screenshot = this._writeScreenshotArtifact(base64, result); + const image = decodeBase64Image(raw, { + allowedTypes: ['image/png', 'image/jpeg'], + }); + const screenshot = await this._writeScreenshotArtifact(image, result, options); const next = { ...result, ...screenshot }; delete next.screenshotDataUrl; delete next.screenshotData; @@ -89,16 +77,19 @@ class DesktopProvider { async _dispatch(command, payload = {}, options = {}) { this._assertReady(); - try { - return this._materialize( - await this.registry.dispatch(this.userId, payload.deviceId || null, command, payload, options), - ); - } catch (error) { - if (error instanceof DesktopCompanionSelectionError || error instanceof DesktopCompanionUnavailableError) { - throw error; - } - throw error; - } + const safePayload = { ...(payload || {}) }; + const signal = options.signal || safePayload.signal || null; + const timeoutMs = options.timeoutMs ?? safePayload.timeoutMs; + delete safePayload.signal; + delete safePayload.timeoutMs; + const result = await this.registry.dispatch( + this.userId, + safePayload.deviceId || null, + command, + safePayload, + { ...options, signal, timeoutMs }, + ); + return this._materialize(result, { signal }); } getStatus() { @@ -121,9 +112,9 @@ class DesktopProvider { return this.registry.revoke(this.userId, deviceId); } - pauseDevice(deviceId, paused = true) { + pauseDevice(deviceId, paused = true, options = {}) { this._assertReady(); - return this.registry.pause(this.userId, deviceId, paused); + return this.registry.pause(this.userId, deviceId, paused, options); } screenshot(options = {}) { @@ -137,7 +128,7 @@ class DesktopProvider { stopStream(options = {}) { this._assertReady(); - return this.registry.stopStream(this.userId, options.deviceId || null); + return this.registry.stopStream(this.userId, options.deviceId || null, options); } observe(options = {}) { @@ -192,6 +183,12 @@ class DesktopProvider { stdin_input: options.stdinInput || null, pty: options.pty === true, inputs: options.inputs || [], + signal: options.signal, + }, { + signal: options.signal, + timeoutMs: Number(options.timeout || 0) > 0 + ? Number(options.timeout) + 10_000 + : undefined, }); } } diff --git a/server/services/desktop/registry.js b/server/services/desktop/registry.js index 0a00fbe6..d443b3e7 100644 --- a/server/services/desktop/registry.js +++ b/server/services/desktop/registry.js @@ -1,5 +1,8 @@ +'use strict'; + const crypto = require('crypto'); const db = require('../../db/database'); +const { createAbortError } = require('../../utils/abort'); const { DESKTOP_COMMANDS, FRAME_TYPE_VIDEO, @@ -66,6 +69,7 @@ class DesktopCompanionRegistry { || DEFAULT_PRESENCE_TOUCH_INTERVAL_MS, ); this.connectionsByUser = new Map(); + this.closed = false; } _getUserMap(userId, create = false) { @@ -199,6 +203,9 @@ class DesktopCompanionRegistry { } registerConnection({ userId, sessionId, ws, hello, remoteAddress = null, userAgent = null }) { + if (this.closed) { + throw new DesktopCompanionUnavailableError('Desktop companion registry is shutting down.'); + } const record = this._upsertDeviceRecord(userId, hello, sessionId); const userMap = this._getUserMap(userId, true); const existing = userMap.get(record.deviceId); @@ -249,11 +256,15 @@ class DesktopCompanionRegistry { // Only mark offline in the DB when this connection is still the active owner. // If a newer connection has already taken over (reconnect race), its // _upsertDeviceRecord already wrote status='online' and we must not clobber it. - this.db.prepare( - `UPDATE desktop_companion_devices - SET status = 'offline', updated_at = datetime('now') - WHERE user_id = ? AND device_id = ?` - ).run(connection.userId, connection.deviceId); + try { + this.db.prepare( + `UPDATE desktop_companion_devices + SET status = 'offline', updated_at = datetime('now') + WHERE user_id = ? AND device_id = ?` + ).run(connection.userId, connection.deviceId); + } catch (error) { + console.warn('[DesktopCompanion] Failed to record disconnect:', error?.message); + } } } @@ -300,18 +311,22 @@ class DesktopCompanionRegistry { const userMap = this._getUserMap(userId); const connection = userMap?.get(String(deviceId)); if (!connection?.isOpen()) return; - this.db.prepare( - `UPDATE desktop_companion_devices - SET status = 'online', - last_seen_at = datetime('now'), - updated_at = datetime('now') - WHERE user_id = ? AND device_id = ? AND revoked_at IS NULL` - ).run(userId, deviceId); + try { + this.db.prepare( + `UPDATE desktop_companion_devices + SET status = 'online', + last_seen_at = datetime('now'), + updated_at = datetime('now') + WHERE user_id = ? AND device_id = ? AND revoked_at IS NULL` + ).run(userId, deviceId); + } catch (error) { + console.warn('[DesktopCompanion] Failed to update presence:', error?.message); + } } isConnected(userId) { const userMap = this._getUserMap(userId); - return userMap != null && userMap.size > 0; + return userMap != null && Array.from(userMap.values()).some((connection) => connection.isOpen()); } getConnection(userId, deviceId) { @@ -394,15 +409,19 @@ class DesktopCompanionRegistry { throw new DesktopCompanionUnavailableError(); } const result = await connection.sendCommand(command, payload, options); - this.touchConnection(userId, device.deviceId, { - label: result?.device?.label, - paused: result?.paused === true, - activeDisplayId: result?.activeDisplayId || result?.device?.activeDisplayId, - permissions: result?.permissions, - capabilities: result?.capabilities, - displays: result?.displays || result?.device?.displays, - metadata: result?.device?.metadata, - }); + try { + this.touchConnection(userId, device.deviceId, { + label: result?.device?.label, + paused: typeof result?.paused === 'boolean' ? result.paused : null, + activeDisplayId: result?.activeDisplayId || result?.device?.activeDisplayId, + permissions: result?.permissions, + capabilities: result?.capabilities, + displays: result?.displays || result?.device?.displays, + metadata: result?.device?.metadata, + }); + } catch (error) { + console.warn('[DesktopCompanion] Failed to record command result:', error?.message); + } return { ...result, device: this.getDeviceRecordByDeviceId(userId, device.deviceId), @@ -429,13 +448,13 @@ class DesktopCompanionRegistry { }; } - async stopStream(userId, deviceId) { + async stopStream(userId, deviceId, options = {}) { const device = this.resolveDevice(userId, deviceId); const connection = this.getConnection(userId, device.deviceId); if (!connection || !connection.isOpen()) { throw new DesktopCompanionUnavailableError(); } - const result = await connection.sendCommand(DESKTOP_COMMANDS.STREAM_STOP, {}); + const result = await connection.sendCommand(DESKTOP_COMMANDS.STREAM_STOP, {}, options); connection._streaming = false; return { ...result, @@ -489,7 +508,7 @@ class DesktopCompanionRegistry { return { success: true, deviceId: normalizedDeviceId }; } - pause(userId, deviceId, paused = true) { + async pause(userId, deviceId, paused = true, options = {}) { const normalizedDeviceId = String(deviceId || '').trim(); if (!normalizedDeviceId) { throw new DesktopCompanionUnavailableError(); @@ -500,27 +519,40 @@ class DesktopCompanionRegistry { if (!existing) { throw new DesktopCompanionUnavailableError(); } - this.db.prepare( - `UPDATE desktop_companion_devices - SET paused = ?, updated_at = datetime('now') - WHERE user_id = ? AND device_id = ?` - ).run(paused ? 1 : 0, userId, normalizedDeviceId); const connection = this.getConnection(userId, normalizedDeviceId); - if (connection) { - void connection.sendCommand('pauseControl', { paused }).catch(() => {}); + if (!connection?.isOpen()) { + throw new DesktopCompanionUnavailableError(); + } + const result = await connection.sendCommand( + DESKTOP_COMMANDS.PAUSE_CONTROL, + { paused }, + options, + ); + if (result?.success !== false) { + try { + this.db.prepare( + `UPDATE desktop_companion_devices + SET paused = ?, updated_at = datetime('now') + WHERE user_id = ? AND device_id = ?` + ).run(paused ? 1 : 0, userId, normalizedDeviceId); + } catch (error) { + console.warn('[DesktopCompanion] Failed to record pause state:', error?.message); + } } return { - success: true, + ...result, + success: result?.success !== false, deviceId: normalizedDeviceId, paused, }; } closeAll() { - for (const userMap of this.connectionsByUser.values()) { - for (const connection of userMap.values()) { - connection.close('server shutdown'); - } + this.closed = true; + const connections = Array.from(this.connectionsByUser.values()) + .flatMap((userMap) => Array.from(userMap.values())); + for (const connection of connections) { + connection.close('server shutdown'); } this.connectionsByUser.clear(); } @@ -555,24 +587,37 @@ class DesktopCompanionConnection { this.lastPongAt = Date.now(); this.lastPresenceTouchAt = 0; this.heartbeatTimer = null; + this.closed = false; - ws.on('message', (data) => this._handleMessage(data)); + ws.on('message', (data) => { + try { + this._handleMessage(data); + } catch (error) { + try { ws.terminate(); } catch {} + this._closePending(error); + } + }); ws.on('pong', () => { this.lastPongAt = Date.now(); this.touchPresence(); }); ws.on('close', () => this._closePending(new DesktopCompanionUnavailableError('Desktop companion disconnected.'))); - ws.on('error', (error) => this._closePending(error)); + ws.on('error', (error) => { + try { ws.terminate(); } catch {} + this._closePending(error); + }); this._startHeartbeat(); } isOpen() { - return this.ws && this.ws.readyState === 1; + return !this.closed && this.ws && this.ws.readyState === 1; } close(reason) { + const wasOpen = this.isOpen(); + this.closed = true; try { - if (this.isOpen()) { + if (wasOpen) { this.ws.close(1000, String(reason || 'closing').slice(0, 120)); } } catch {} @@ -602,19 +647,51 @@ class DesktopCompanionConnection { return Promise.reject(new DesktopCompanionUnavailableError()); } const id = crypto.randomUUID(); - const timeoutMs = Number(options.timeoutMs || this.timeoutMs); + const requestedTimeout = Number(options.timeoutMs || this.timeoutMs); + const timeoutMs = Number.isFinite(requestedTimeout) + ? Math.min(30 * 60 * 1000, Math.max(100, requestedTimeout)) + : DEFAULT_COMMAND_TIMEOUT_MS; const message = createDesktopCommandMessage(id, command, payload); return new Promise((resolve, reject) => { - const timer = setTimeout(() => { + const signal = options.signal || null; + const sendCancellation = () => { + if (!this.isOpen() || command === DESKTOP_COMMANDS.CANCEL_COMMAND) return; + try { + this.ws.send(JSON.stringify(createDesktopCommandMessage( + crypto.randomUUID(), + DESKTOP_COMMANDS.CANCEL_COMMAND, + { commandId: id }, + ))); + } catch {} + }; + const cleanup = () => { + clearTimeout(timer); + signal?.removeEventListener('abort', onAbort); this.pending.delete(id); - reject(new Error(`Desktop companion command timed out: ${command}`)); + }; + const onAbort = () => { + cleanup(); + sendCancellation(); + reject(createAbortError(signal, `Desktop companion command aborted: ${command}`)); + }; + const timer = setTimeout(() => { + cleanup(); + sendCancellation(); + const error = new Error(`Desktop companion command timed out: ${command}`); + error.code = 'DESKTOP_COMPANION_COMMAND_TIMEOUT'; + reject(error); }, timeoutMs); - this.pending.set(id, { resolve, reject, timer, command }); + timer.unref?.(); + if (signal?.aborted) { + onAbort(); + return; + } + signal?.addEventListener('abort', onAbort, { once: true }); + this.pending.set(id, { resolve, reject, timer, command, signal, onAbort }); try { this.ws.send(JSON.stringify(message)); } catch (error) { - clearTimeout(timer); - this.pending.delete(id); + cleanup(); reject(error); } }); @@ -636,10 +713,14 @@ class DesktopCompanionConnection { if (message.type === 'event') { if (message.event === 'statusChanged' || message.event === 'permissionsChanged') { - this.registry.touchConnection(this.userId, this.deviceId, { - ...message.payload, - metadata: message.payload?.metadata, - }); + try { + this.registry.touchConnection(this.userId, this.deviceId, { + ...message.payload, + metadata: message.payload?.metadata, + }); + } catch (error) { + console.warn('[DesktopCompanion] Failed to record device event:', error?.message); + } } return; } @@ -648,6 +729,7 @@ class DesktopCompanionConnection { const pending = this.pending.get(message.id); if (!pending) return; clearTimeout(pending.timer); + pending.signal?.removeEventListener('abort', pending.onAbort); this.pending.delete(message.id); if (message.ok === false) { const error = new Error(String(message.error || `Desktop companion command failed: ${pending.command}`)); @@ -655,16 +737,18 @@ class DesktopCompanionConnection { pending.reject(error); return; } - pending.resolve(message.payload || {}); + pending.resolve(message.payload ?? {}); } _closePending(error) { + this.closed = true; if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } for (const pending of this.pending.values()) { clearTimeout(pending.timer); + pending.signal?.removeEventListener('abort', pending.onAbort); pending.reject(error); } this.pending.clear(); @@ -702,5 +786,6 @@ class DesktopCompanionConnection { } module.exports = { + DesktopCompanionConnection, DesktopCompanionRegistry, }; diff --git a/server/services/integrations/figma/provider.js b/server/services/integrations/figma/provider.js index 661191ab..9c83b3b6 100644 --- a/server/services/integrations/figma/provider.js +++ b/server/services/integrations/figma/provider.js @@ -158,25 +158,88 @@ function figmaUrl(path, query) { return url.toString(); } -async function figmaRequest(credentials, { method = 'GET', path, query, body }) { - return fetchJson( +function expiresAtFromSeconds(expiresIn) { + return new Date( + Date.now() + Math.max(1, Number(expiresIn) || 3600) * 1000, + ).toISOString(); +} + +function tokenExpiresSoon(credentials) { + const expiresAt = Date.parse(String(credentials?.expires_at || '')); + return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 60 * 1000; +} + +async function refreshFigmaCredentials(credentials, signal) { + const refreshToken = String(credentials?.refresh_token || '').trim(); + if (!refreshToken) { + throw new Error('Figma refresh token is missing. Reconnect this integration account.'); + } + const config = resolveFigmaOAuthConfig(); + const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString( + 'base64', + ); + const token = await fetchJson( + 'https://api.figma.com/v1/oauth/token', + { + method: 'POST', + headers: { Authorization: `Basic ${basic}` }, + form: { + grant_type: 'refresh_token', + refresh_token: refreshToken, + }, + signal, + }, + { serviceName: 'Figma token refresh' }, + ); + if (!String(token?.access_token || '').trim()) { + throw new Error('Figma token refresh did not return an access token.'); + } + return { + ...credentials, + access_token: token.access_token, + refresh_token: token.refresh_token || refreshToken, + token_type: token.token_type || credentials.token_type || 'bearer', + expires_in: token.expires_in, + expires_at: expiresAtFromSeconds(token.expires_in), + }; +} + +async function figmaRequest(context, { method = 'GET', path, query, body }) { + const { signal } = context; + let credentials = context.credentials; + if (tokenExpiresSoon(credentials)) { + credentials = await refreshFigmaCredentials(credentials, signal); + context.updateCredentials(credentials); + } + + const performRequest = (activeCredentials) => fetchJson( figmaUrl(path, query), { method: String(method || 'GET').toUpperCase(), - headers: { Authorization: `Bearer ${credentials.access_token}` }, + headers: { Authorization: `Bearer ${activeCredentials.access_token}` }, ...(body === undefined ? {} : { json: body }), + signal, }, { serviceName: 'Figma' }, ); + + try { + return await performRequest(credentials); + } catch (error) { + if (error?.status !== 401 || !credentials.refresh_token) throw error; + credentials = await refreshFigmaCredentials(credentials, signal); + context.updateCredentials(credentials); + return performRequest(credentials); + } } -async function executeFigmaTool(toolName, args, { credentials }) { +async function executeFigmaTool(toolName, args, context) { switch (toolName) { case 'figma_get_me': - return { result: await figmaRequest(credentials, { path: '/v1/me' }) }; + return { result: await figmaRequest(context, { path: '/v1/me' }) }; case 'figma_get_file': return { - result: await figmaRequest(credentials, { + result: await figmaRequest(context, { path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}`, query: { ids: args.ids || undefined, @@ -186,14 +249,14 @@ async function executeFigmaTool(toolName, args, { credentials }) { }; case 'figma_get_file_nodes': return { - result: await figmaRequest(credentials, { + result: await figmaRequest(context, { path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}/nodes`, query: { ids: requireText(args.ids, 'ids') }, }), }; case 'figma_get_file_images': return { - result: await figmaRequest(credentials, { + result: await figmaRequest(context, { path: `/v1/images/${encodeURIComponent(requireText(args.file_key, 'file_key'))}`, query: { ids: requireText(args.ids, 'ids'), @@ -204,13 +267,13 @@ async function executeFigmaTool(toolName, args, { credentials }) { }; case 'figma_get_comments': return { - result: await figmaRequest(credentials, { + result: await figmaRequest(context, { path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}/comments`, }), }; case 'figma_post_comment': return { - result: await figmaRequest(credentials, { + result: await figmaRequest(context, { method: 'POST', path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}/comments`, body: { @@ -221,7 +284,7 @@ async function executeFigmaTool(toolName, args, { credentials }) { }; case 'figma_api_request': return { - result: await figmaRequest(credentials, { + result: await figmaRequest(context, { method: args.method, path: requireText(args.path, 'path'), query: args.query, @@ -263,7 +326,7 @@ function createFigmaProvider() { appId: app.id, }; }, - async finishOAuth({ code, app }) { + async finishOAuth({ code, app, signal }) { const config = resolveFigmaOAuthConfig(); const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString( 'base64', @@ -280,6 +343,7 @@ function createFigmaProvider() { code, grant_type: 'authorization_code', }, + signal, }, { serviceName: 'Figma' }, ); @@ -290,6 +354,7 @@ function createFigmaProvider() { headers: { Authorization: `Bearer ${token.access_token}`, }, + signal, }, { serviceName: 'Figma' }, ); @@ -307,6 +372,7 @@ function createFigmaProvider() { access_token: token.access_token, refresh_token: token.refresh_token, expires_in: token.expires_in, + expires_at: expiresAtFromSeconds(token.expires_in), token_type: token.token_type, scope: token.scope, }, diff --git a/server/services/integrations/github/common.js b/server/services/integrations/github/common.js index eb4ad8a9..8adc9b82 100644 --- a/server/services/integrations/github/common.js +++ b/server/services/integrations/github/common.js @@ -1,6 +1,7 @@ 'use strict'; const crypto = require('crypto'); +const { fetchResponseText } = require('../http'); function base64UrlSha256(value) { return crypto @@ -46,15 +47,19 @@ async function githubApiRequest(auth, options = {}) { headers['Content-Type'] = 'application/json'; } - const response = await fetch(url.toString(), { - method, - headers, - body: body ? JSON.stringify(body) : undefined, - }); + const { response, text: rawBody } = await fetchResponseText( + url.toString(), + { + method, + headers, + body: body ? JSON.stringify(body) : undefined, + signal: options.signal || auth?.signal, + }, + { serviceName: 'GitHub API' }, + ); let data = null; if (response.status !== 204 && response.status !== 205) { - const rawBody = await response.text(); if (rawBody.trim()) { try { data = JSON.parse(rawBody); diff --git a/server/services/integrations/github/provider.js b/server/services/integrations/github/provider.js index 787b7600..3f1b034a 100644 --- a/server/services/integrations/github/provider.js +++ b/server/services/integrations/github/provider.js @@ -6,6 +6,8 @@ const { decryptValue } = require('../secrets'); const { getConnectionAccessMode } = require('../access'); const { githubToolDefinitions, executeGithubTool } = require('./repos'); const { base64UrlSha256 } = require('./common'); +const { fetchJson } = require('../oauth_provider'); +const { fetchResponseText } = require('../http'); const GITHUB_ACCOUNT_IDENTITY_SCOPES = [ 'read:user', @@ -159,8 +161,9 @@ function summarizeAppConnection(app, connectionRows, envStatus) { }; } -async function executeGithubRepoTool(toolName, args, connection) { +async function executeGithubRepoTool(toolName, args, connection, executionOptions = {}) { const auth = await buildAuthorizedClient(connection); + auth.signal = executionOptions.signal || null; if (!auth.token) { throw new Error('GitHub access token is missing or expired. Please reconnect your GitHub account.'); } @@ -326,40 +329,32 @@ function createGithubProvider() { const url = `https://github.com/login/oauth/authorize?${params.toString()}`; return { url, appId: app.id }; }, - async finishOAuth({ code, codeVerifier, appKey }) { + async finishOAuth({ code, codeVerifier, appKey, signal }) { const app = getApp(appKey); if (!app) { throw new Error(`Unknown GitHub app: ${appKey}`); } const { config } = createOAuthClient(); - const tokenResponse = await fetch('https://github.com/login/oauth/access_token', { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', + const tokenData = await fetchJson( + 'https://github.com/login/oauth/access_token', + { + method: 'POST', + form: { + client_id: config.clientId, + client_secret: config.clientSecret, + code, + code_verifier: codeVerifier, + redirect_uri: config.redirectUri, + }, + signal, }, - body: new URLSearchParams({ - client_id: config.clientId, - client_secret: config.clientSecret, - code, - code_verifier: codeVerifier, - redirect_uri: config.redirectUri, - }).toString(), - }); - - const tokenBody = await tokenResponse.text(); - let tokenData = {}; - try { - tokenData = tokenBody ? JSON.parse(tokenBody) : {}; - } catch { - tokenData = {}; - } - if (!tokenResponse.ok) { - throw new Error(`GitHub OAuth token exchange failed (${tokenResponse.status}): ${tokenBody || 'No response body'}`); - } - if (tokenData.error) { - throw new Error(`GitHub OAuth error: ${tokenData.error_description || tokenData.error}`); + { serviceName: 'GitHub OAuth token exchange' }, + ); + if (tokenData?.error) { + throw new Error( + `GitHub OAuth error: ${tokenData.error_description || tokenData.error}`, + ); } const accessToken = tokenData.access_token; @@ -367,17 +362,17 @@ function createGithubProvider() { throw new Error('GitHub OAuth did not return an access token.'); } - const userResponse = await fetch('https://api.github.com/user', { - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Accept': 'application/vnd.github.v3+json', + const userData = await fetchJson( + 'https://api.github.com/user', + { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Accept': 'application/vnd.github.v3+json', + }, + signal, }, - }); - if (!userResponse.ok) { - const errorBody = await userResponse.text().catch(() => 'Unknown error'); - throw new Error(`GitHub user profile request failed (${userResponse.status}): ${errorBody}`); - } - const userData = await userResponse.json(); + { serviceName: 'GitHub user profile' }, + ); const accountEmail = String(userData.login || '').trim(); if (!accountEmail) { throw new Error('GitHub API did not return a user login.'); @@ -398,7 +393,7 @@ function createGithubProvider() { }, }; }, - async disconnect(connectionRow) { + async disconnect(connectionRow, executionOptions = {}) { if (!connectionRow?.credentials_json) return; try { const credentials = JSON.parse(decryptValue(connectionRow.credentials_json)); @@ -415,20 +410,24 @@ function createGithubProvider() { const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64'); const revokeUrl = `https://api.github.com/applications/${encodeURIComponent(clientId)}/token`; - const revokeResponse = await fetch(revokeUrl, { - method: 'DELETE', - headers: { - 'Authorization': `Basic ${basic}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json', + const { response: revokeResponse, text: revokeBody } = await fetchResponseText( + revokeUrl, + { + method: 'DELETE', + headers: { + 'Authorization': `Basic ${basic}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + access_token: accessToken, + }), + signal: executionOptions.signal || null, }, - body: JSON.stringify({ - access_token: accessToken, - }), - }); + { serviceName: 'GitHub token revocation' }, + ); if (!revokeResponse.ok) { - const revokeBody = await revokeResponse.text().catch(() => 'Unknown error'); console.warn( `[GitHub] Failed to revoke token for disconnect (connection ${connectionRow?.id || 'unknown'}): ${revokeResponse.status} ${revokeBody}`, ); @@ -439,8 +438,8 @@ function createGithubProvider() { ); } }, - async executeTool(toolName, args, connectionRow) { - return executeGithubRepoTool(toolName, args, connectionRow); + async executeTool(toolName, args, connectionRow, executionOptions = {}) { + return executeGithubRepoTool(toolName, args, connectionRow, executionOptions); }, summarizeConnection(connectionRows) { const snapshot = this.buildSnapshot(connectionRows); @@ -496,4 +495,4 @@ module.exports = { scopes: scopes.slice(), }), ), -}; \ No newline at end of file +}; diff --git a/server/services/integrations/google/provider.js b/server/services/integrations/google/provider.js index d4476340..76f4dfd6 100644 --- a/server/services/integrations/google/provider.js +++ b/server/services/integrations/google/provider.js @@ -10,6 +10,7 @@ const { calendarToolDefinitions, executeCalendarTool } = require('./calendar'); const { driveToolDefinitions, executeDriveTool } = require('./drive'); const { docsToolDefinitions, executeDocsTool } = require('./docs'); const { sheetsToolDefinitions, executeSheetsTool } = require('./sheets'); +const { waitForAbortableResult } = require('../http'); const GOOGLE_ACCOUNT_IDENTITY_SCOPES = [ 'openid', @@ -118,7 +119,17 @@ function createOAuthClient() { }; } -async function buildAuthorizedClient(connection) { +function attachAbortSignal(client, signal) { + if (!signal) return client; + const request = client.transporter.request.bind(client.transporter); + client.transporter.request = (requestOptions = {}) => request({ + ...requestOptions, + signal: requestOptions.signal || signal, + }); + return client; +} + +async function buildAuthorizedClient(connection, options = {}) { const { client } = createOAuthClient(); let credentials = {}; try { @@ -142,7 +153,7 @@ async function buildAuthorizedClient(connection) { }); } }); - return client; + return attachAbortSignal(client, options.signal); } function getApp(appId) { @@ -247,21 +258,29 @@ function summarizeAppConnection(app, connectionRows, envStatus) { }; } -async function executeGoogleWorkspaceTool(toolName, args, connection) { - const auth = await buildAuthorizedClient(connection); +async function executeGoogleWorkspaceTool( + toolName, + args, + connection, + executionOptions = {}, +) { + const auth = await buildAuthorizedClient(connection, executionOptions); const appId = toolAppMap.get(toolName); const app = getApp(appId); if (!app) { throw new Error(`Unknown tool: ${toolName}`); } - const result = await app.executor(toolName, args, auth); + const result = await waitForAbortableResult( + app.executor(toolName, args, auth), + executionOptions.signal, + ); if (result === null) { throw new Error(`Unknown tool: ${toolName}`); } return { result, credentials: auth.credentials }; } -async function collectGoogleMemoryDocuments({ connection, sourceTypes = [] }) { +async function collectGoogleMemoryDocuments({ connection, sourceTypes = [], signal = null }) { const appKey = String(connection?.app_key || '').trim(); const documents = []; const collectedAt = new Date().toISOString(); @@ -271,6 +290,7 @@ async function collectGoogleMemoryDocuments({ connection, sourceTypes = [] }) { 'google_workspace_gmail_search_threads', { query: 'newer_than:7d', max_results: 8 }, connection, + { signal }, ); for (const thread of Array.isArray(result?.threads) ? result.threads : []) { const subject = String(thread.subject || 'Gmail thread').trim(); @@ -300,6 +320,7 @@ async function collectGoogleMemoryDocuments({ connection, sourceTypes = [] }) { 'google_workspace_calendar_list_events', { time_min: timeMin, time_max: timeMax, max_results: 12 }, connection, + { signal }, ); for (const event of Array.isArray(result?.events) ? result.events : []) { const title = String(event.summary || 'Calendar event').trim(); @@ -489,19 +510,23 @@ function createGoogleWorkspaceProvider() { }); return { url, appId: app.id }; }, - async finishOAuth({ code, codeVerifier, appKey }) { + async finishOAuth({ code, codeVerifier, appKey, signal }) { const app = getApp(appKey); if (!app) { throw new Error(`Unknown Google Workspace app: ${appKey}`); } - const { client } = createOAuthClient(); - const { tokens } = await client.getToken({ - code, - codeVerifier, - }); + const oauthClient = createOAuthClient(); + const client = attachAbortSignal(oauthClient.client, signal); + const { tokens } = await waitForAbortableResult( + client.getToken({ + code, + codeVerifier, + }), + signal, + ); client.setCredentials(tokens); const oauth2 = google.oauth2({ version: 'v2', auth: client }); - const profile = await oauth2.userinfo.get(); + const profile = await waitForAbortableResult(oauth2.userinfo.get(), signal); const accountEmail = String(profile.data.email || '').trim(); if (!accountEmail) { throw new Error('Google OAuth did not return an account email address.'); @@ -523,26 +548,37 @@ function createGoogleWorkspaceProvider() { }, }; }, - async disconnect(connectionRow) { - const auth = await buildAuthorizedClient(connectionRow); + async disconnect(connectionRow, executionOptions = {}) { + const auth = await buildAuthorizedClient(connectionRow, executionOptions); const refreshToken = auth.credentials.refresh_token; const accessToken = auth.credentials.access_token; if (refreshToken) { - await auth.revokeToken(refreshToken).catch((error) => { + await waitForAbortableResult( + auth.revokeToken(refreshToken), + executionOptions.signal, + ).catch((error) => { console.warn( `[Google Workspace] Failed to revoke refresh token for disconnect (connection ${connectionRow?.id || 'unknown'}): ${error?.message || error}`, ); }); } else if (accessToken) { - await auth.revokeToken(accessToken).catch((error) => { + await waitForAbortableResult( + auth.revokeToken(accessToken), + executionOptions.signal, + ).catch((error) => { console.warn( `[Google Workspace] Failed to revoke access token for disconnect (connection ${connectionRow?.id || 'unknown'}): ${error?.message || error}`, ); }); } }, - async executeTool(toolName, args, connectionRow) { - return executeGoogleWorkspaceTool(toolName, args, connectionRow); + async executeTool(toolName, args, connectionRow, executionOptions = {}) { + return executeGoogleWorkspaceTool( + toolName, + args, + connectionRow, + executionOptions, + ); }, async collectMemoryDocuments(options) { return collectGoogleMemoryDocuments(options); diff --git a/server/services/integrations/home_assistant/network.js b/server/services/integrations/home_assistant/network.js index 24574a0e..91b2aef3 100644 --- a/server/services/integrations/home_assistant/network.js +++ b/server/services/integrations/home_assistant/network.js @@ -2,8 +2,11 @@ const dns = require('dns').promises; const net = require('net'); +const { + fetchResponseText, + waitForAbortableResult, +} = require('../http'); -const HTTP_TIMEOUT_MS = 15000; const ALLOWED_PORTS = new Set(['', '443', '8123']); function trimText(value) { @@ -100,7 +103,7 @@ function isBlockedIpAddress(address) { return true; } -async function assertPublicHomeAssistantEndpoint(baseUrl) { +async function assertPublicHomeAssistantEndpoint(baseUrl, options = {}) { const url = new URL(normalizeHomeAssistantBaseUrl(baseUrl)); const hostname = String(url.hostname || '').replace(/^\[|\]$/g, ''); if (net.isIP(hostname) && isBlockedIpAddress(hostname)) { @@ -109,7 +112,10 @@ async function assertPublicHomeAssistantEndpoint(baseUrl) { let addresses; try { - addresses = await dns.lookup(hostname, { all: true, verbatim: true }); + addresses = await waitForAbortableResult( + dns.lookup(hostname, { all: true, verbatim: true }), + options.signal, + ); } catch (error) { throw new Error(`Could not resolve Home Assistant host: ${error?.message || 'DNS lookup failed'}`); } @@ -140,7 +146,7 @@ function buildHomeAssistantUrl(baseUrl, path, query = {}) { async function homeAssistantRequest(credentials, options = {}) { const baseUrl = normalizeHomeAssistantBaseUrl(credentials.baseUrl); const token = requireText(credentials.token, 'Home Assistant token'); - await assertPublicHomeAssistantEndpoint(baseUrl); + await assertPublicHomeAssistantEndpoint(baseUrl, { signal: options.signal }); const method = String(options.method || 'GET').trim().toUpperCase(); if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) { @@ -154,31 +160,22 @@ async function homeAssistantRequest(credentials, options = {}) { body = JSON.stringify(options.body); } - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS); - let response; - try { - response = await fetch(buildHomeAssistantUrl(baseUrl, options.path, options.query), { + const { response, text } = await fetchResponseText( + buildHomeAssistantUrl(baseUrl, options.path, options.query), + { method, headers, body, redirect: 'manual', - signal: controller.signal, - }); - } catch (error) { - if (error?.name === 'AbortError') { - throw new Error(`Home Assistant request timed out after ${HTTP_TIMEOUT_MS}ms.`); - } - throw error; - } finally { - clearTimeout(timer); - } + signal: options.signal, + }, + { serviceName: 'Home Assistant' }, + ); if (response.status >= 300 && response.status < 400) { throw new Error('Home Assistant redirected the API request; redirects are not followed.'); } - const text = await response.text(); let data = null; try { data = text ? JSON.parse(text) : null; diff --git a/server/services/integrations/home_assistant/provider.js b/server/services/integrations/home_assistant/provider.js index d9927489..fbbd4ff5 100644 --- a/server/services/integrations/home_assistant/provider.js +++ b/server/services/integrations/home_assistant/provider.js @@ -172,8 +172,10 @@ function createHomeAssistantProvider() { } return 'Home Assistant: native Home Assistant access is connected in this run with tools for config, states, entity state, service calls, and /api requests.'; }, - async executeTool(toolName, args, connection) { - return executeHomeAssistantTool(toolName, args, connection); + async executeTool(toolName, args, connection, executionOptions = {}) { + return executeHomeAssistantTool(toolName, args, connection, { + signal: executionOptions.signal || null, + }); }, getUserConfig({ userId, agentId }) { const normalizedUserId = Number(userId); @@ -186,7 +188,7 @@ function createHomeAssistantProvider() { hasConnectedAccount: connection?.status === 'connected', }; }, - async saveUserConfig({ userId, agentId, config }) { + async saveUserConfig({ userId, agentId, config, signal }) { const normalizedUserId = Number(userId); if (!Number.isInteger(normalizedUserId) || normalizedUserId <= 0) { throw new Error('A valid user is required to save Home Assistant configuration.'); @@ -203,10 +205,10 @@ function createHomeAssistantProvider() { if (!token) throw new Error('Home Assistant Long-Lived Access Token is required.'); const credentials = { baseUrl, token }; - await assertPublicHomeAssistantEndpoint(baseUrl); + await assertPublicHomeAssistantEndpoint(baseUrl, { signal }); let haConfig; try { - haConfig = await fetchHomeAssistantConfig(credentials); + haConfig = await fetchHomeAssistantConfig(credentials, { signal }); } catch (error) { const message = String(error?.message || '').toLowerCase(); if (message.includes('401') || message.includes('unauthorized')) { diff --git a/server/services/integrations/home_assistant/tools.js b/server/services/integrations/home_assistant/tools.js index 1a76a135..d7beb4fd 100644 --- a/server/services/integrations/home_assistant/tools.js +++ b/server/services/integrations/home_assistant/tools.js @@ -3,8 +3,12 @@ const { decryptValue } = require('../secrets'); const { homeAssistantRequest, normalizeHomeAssistantBaseUrl, requireText, trimText } = require('./network'); -async function fetchHomeAssistantConfig(credentials) { - return homeAssistantRequest(credentials, { method: 'GET', path: '/api/config' }); +async function fetchHomeAssistantConfig(credentials, options = {}) { + return homeAssistantRequest(credentials, { + method: 'GET', + path: '/api/config', + signal: options.signal, + }); } function parseCredentials(connection) { @@ -46,16 +50,21 @@ function validateServiceSegment(value, label) { return text; } -async function executeHomeAssistantTool(toolName, args, connection) { +async function executeHomeAssistantTool(toolName, args, connection, options = {}) { const credentials = connectionCredentials(connection); + const signal = options.signal || null; switch (toolName) { case 'home_assistant_get_config': - return { result: await fetchHomeAssistantConfig(credentials) }; + return { result: await fetchHomeAssistantConfig(credentials, { signal }) }; case 'home_assistant_list_states': return { result: filterStates( - await homeAssistantRequest(credentials, { method: 'GET', path: '/api/states' }), + await homeAssistantRequest(credentials, { + method: 'GET', + path: '/api/states', + signal, + }), args, ), }; @@ -64,6 +73,7 @@ async function executeHomeAssistantTool(toolName, args, connection) { result: await homeAssistantRequest(credentials, { method: 'GET', path: `/api/states/${encodeURIComponent(requireText(args.entity_id, 'entity_id'))}`, + signal, }), }; case 'home_assistant_call_service': { @@ -77,6 +87,7 @@ async function executeHomeAssistantTool(toolName, args, connection) { method: 'POST', path: `/api/services/${domain}/${service}`, body: serviceData, + signal, }), }; } @@ -87,6 +98,7 @@ async function executeHomeAssistantTool(toolName, args, connection) { path: requireText(args.path, 'path'), query: args.query && typeof args.query === 'object' ? args.query : {}, body: args.body && typeof args.body === 'object' ? args.body : undefined, + signal, }), }; default: diff --git a/server/services/integrations/http.js b/server/services/integrations/http.js new file mode 100644 index 00000000..d6a871a6 --- /dev/null +++ b/server/services/integrations/http.js @@ -0,0 +1,51 @@ +'use strict'; + +const { createAbortError } = require('../../utils/abort'); +const { + DEFAULT_MAX_RESPONSE_BYTES, + fetchResponseText: fetchBoundedResponseText, + readResponseText: readBoundedResponseText, + waitForAbortableResult, + waitForBoundedResult: waitForBoundedIo, +} = require('../network/http'); + +const DEFAULT_INTEGRATION_HTTP_TIMEOUT_MS = 15000; + +function abortError(signal, fallback = 'Integration request aborted.') { + return createAbortError(signal, fallback); +} + +async function readResponseText(response, options = {}) { + return readBoundedResponseText(response, { + ...options, + serviceName: String(options.serviceName || 'Integration').trim() || 'Integration', + tooLargeCode: 'INTEGRATION_RESPONSE_TOO_LARGE', + }); +} + +async function fetchResponseText(url, options = {}, context = {}) { + return fetchBoundedResponseText(url, { + ...options, + serviceName: String(context.serviceName || 'Integration').trim() || 'Integration', + timeoutCode: 'INTEGRATION_HTTP_TIMEOUT', + tooLargeCode: 'INTEGRATION_RESPONSE_TOO_LARGE', + }); +} + +async function waitForBoundedResult(promise, options = {}) { + return waitForBoundedIo(promise, { + ...options, + serviceName: String(options.serviceName || 'Integration').trim() || 'Integration', + timeoutCode: 'INTEGRATION_HTTP_TIMEOUT', + }); +} + +module.exports = { + DEFAULT_INTEGRATION_HTTP_TIMEOUT_MS, + DEFAULT_MAX_RESPONSE_BYTES, + abortError, + fetchResponseText, + readResponseText, + waitForAbortableResult, + waitForBoundedResult, +}; diff --git a/server/services/integrations/manager.js b/server/services/integrations/manager.js index 7a791282..8a1e6f87 100644 --- a/server/services/integrations/manager.js +++ b/server/services/integrations/manager.js @@ -10,6 +10,11 @@ const { normalizeAccessMode, withConnectionAccessMode, } = require('./access'); +const { + abortError, + waitForAbortableResult, +} = require('./http'); +const { throwIfAborted } = require('../../utils/abort'); const OAUTH_STATE_PATTERN = /^[a-f0-9]{32,128}$/i; @@ -56,6 +61,7 @@ class IntegrationManager { this.registry = createIntegrationRegistry({ io: this.app?.locals?.io || null, }); + this.connectionExecutionQueues = new Map(); } parseCredentials(credentialsJson) { @@ -152,6 +158,23 @@ class IntegrationManager { ).run(); } + claimOAuthState(state) { + return db.transaction(() => { + const stateRow = db + .prepare( + `SELECT * FROM integration_oauth_states + WHERE state = ? AND datetime(expires_at) > datetime('now')`, + ) + .get(state); + if (!stateRow) return null; + const deleted = db.prepare( + `DELETE FROM integration_oauth_states + WHERE state = ? AND datetime(expires_at) > datetime('now')`, + ).run(state); + return deleted.changes === 1 ? stateRow : null; + })(); + } + listConnections(userId, providerKey = null, agentId = null) { const scopedAgentId = resolveAgentId(userId, agentId); const query = providerKey @@ -225,6 +248,7 @@ class IntegrationManager { userId, agentId, appKey, + signal: options.signal || null, }); const url = String(result?.url || '').trim(); const absoluteUrl = url.startsWith('http') @@ -247,6 +271,7 @@ class IntegrationManager { userId, agentId, appKey, + signal: options.signal || null, }); db.prepare( @@ -290,7 +315,8 @@ class IntegrationManager { ); } - async finishOAuth(state, code) { + async finishOAuth(state, code, options = {}) { + throwIfAborted(options.signal, 'OAuth callback request aborted.'); this.cleanupExpiredOauthStates(); const normalizedState = String(state || '').trim(); if (!OAUTH_STATE_PATTERN.test(normalizedState)) { @@ -300,12 +326,7 @@ class IntegrationManager { if (!normalizedCode) { throw new Error('OAuth authorization code is required.'); } - const stateRow = db - .prepare( - `SELECT * FROM integration_oauth_states - WHERE state = ? AND datetime(expires_at) > datetime('now')`, - ) - .get(normalizedState); + const stateRow = this.claimOAuthState(normalizedState); if (!stateRow) { throw new Error('OAuth state is missing or expired.'); } @@ -322,6 +343,7 @@ class IntegrationManager { code: normalizedCode, codeVerifier: decryptValue(stateRow.code_verifier), appKey: stateRow.app_key, + signal: options.signal || null, }); const mergedCredentials = this.mergeWithReusableCredentials( @@ -386,10 +408,6 @@ class IntegrationManager { result.accountEmail, ); - db.prepare('DELETE FROM integration_oauth_states WHERE state = ?').run( - stateRow.state, - ); - return { provider: provider.key, appId: stateRow.app_key, @@ -420,7 +438,11 @@ class IntegrationManager { }; } - await provider.disconnect(connection).catch(() => {}); + if (typeof provider.disconnect === 'function') { + await provider.disconnect(connection, { + signal: options.signal || null, + }).catch(() => {}); + } db.prepare('DELETE FROM integration_connections WHERE user_id = ? AND agent_id = ? AND id = ?').run( userId, agentId, @@ -636,7 +658,46 @@ class IntegrationManager { }; } - async executeTool(userId, toolName, args, agentId = null) { + connectionExecutionKey(connection) { + return [ + connection.user_id, + connection.agent_id, + connection.provider_key, + String(connection.account_email || '').trim().toLowerCase(), + ].join(':'); + } + + async acquireConnectionExecution(connection, signal) { + const key = this.connectionExecutionKey(connection); + const previous = this.connectionExecutionQueues.get(key) || Promise.resolve(); + let releaseGate; + const gate = new Promise((resolve) => { + releaseGate = resolve; + }); + const tail = previous.catch(() => {}).then(() => gate); + this.connectionExecutionQueues.set(key, tail); + void tail.finally(() => { + if (this.connectionExecutionQueues.get(key) === tail) { + this.connectionExecutionQueues.delete(key); + } + }); + + try { + await waitForAbortableResult(previous.catch(() => {}), signal); + } catch (error) { + releaseGate(); + throw error; + } + + let released = false; + return () => { + if (released) return; + released = true; + releaseGate(); + }; + } + + async executeTool(userId, toolName, args, agentId = null, options = {}) { let foundSupportingProvider = false; for (const provider of this.registry.list()) { if (!provider.supportsTool(toolName)) continue; @@ -661,54 +722,86 @@ class IntegrationManager { }; } - let execution; + const storedCredentials = this.parseCredentials( + selection.connection.credentials_json, + ); + const needsCredentialLock = + provider.requiresRefreshToken === true || + Boolean(storedCredentials.refresh_token); + const releaseExecution = needsCredentialLock + ? await this.acquireConnectionExecution(selection.connection, options.signal) + : () => {}; try { - execution = await provider.executeTool( - toolName, - args, - selection.connection, + const connection = this.getConnectionById( + userId, + selection.connection.id, + agentId, ); - } catch (err) { - if (isLikelyExpiredConnectionError(err)) { - db.prepare( - `UPDATE integration_connections - SET status = 'expired', updated_at = datetime('now') - WHERE id = ? AND user_id = ? AND agent_id = ?`, - ).run( - selection.connection.id, + if (!connection || connection.status !== 'connected') { + return { + error: `The selected ${provider.label} connection is no longer available.`, + }; + } + + let execution; + try { + execution = await provider.executeTool( + toolName, + args, + connection, + { signal: options.signal || null }, + ); + } catch (err) { + if ( + options.signal?.aborted || + err?.name === 'AbortError' || + err?.code === 'ABORT_ERR' + ) { + throw options.signal?.aborted ? abortError(options.signal) : err; + } + if (isLikelyExpiredConnectionError(err)) { + db.prepare( + `UPDATE integration_connections + SET status = 'expired', updated_at = datetime('now') + WHERE id = ? AND user_id = ? AND agent_id = ?`, + ).run( + connection.id, + userId, + resolveAgentId(userId, agentId), + ); + return { + error: `Your ${provider.label} account connection has expired and needs to be reconnected. Open Official Integrations to reconnect your account.`, + expired: true, + connectionId: connection.id, + }; + } + return { error: err?.message || 'execution_error' }; + } + if (!execution) { + return { error: 'execution_failed' }; + } + + if (execution.credentials) { + const existingCredentials = this.parseCredentials( + connection.credentials_json, + ); + const mergedCredentials = this.mergeUpdatedCredentials( + existingCredentials, + execution.credentials, + ); + this.persistSharedCredentials( userId, resolveAgentId(userId, agentId), + provider.key, + connection.account_email, + mergedCredentials, ); - return { - error: `Your ${provider.label} account connection has expired and needs to be reconnected. Open Official Integrations to reconnect your account.`, - expired: true, - connectionId: selection.connection.id, - }; } - return { error: err?.message || 'execution_error' }; - } - if (!execution) { - return { error: 'execution_failed' }; - } - if (execution.credentials) { - const existingCredentials = this.parseCredentials( - selection.connection.credentials_json, - ); - const mergedCredentials = this.mergeUpdatedCredentials( - existingCredentials, - execution.credentials, - ); - this.persistSharedCredentials( - userId, - resolveAgentId(userId, agentId), - provider.key, - selection.connection.account_email, - mergedCredentials, - ); + return execution.result; + } finally { + releaseExecution(); } - - return execution.result; } return foundSupportingProvider @@ -716,6 +809,18 @@ class IntegrationManager { : { error: 'no_provider_support' }; } + async shutdown() { + const providers = this.registry.list(); + await Promise.allSettled( + providers.map((provider) => { + if (typeof provider.shutdown !== 'function') return null; + return provider.shutdown(); + }), + ); + this.connectionExecutionQueues.clear(); + return { state: 'stopped', providerCount: providers.length }; + } + summarizeConnectedProviders(userId, agentId = null) { const scopedAgentId = resolveAgentId(userId, agentId); const ingestionService = this.app?.locals?.memoryIngestionService || null; diff --git a/server/services/integrations/microsoft/provider.js b/server/services/integrations/microsoft/provider.js index 43ba360e..4ed73d5c 100644 --- a/server/services/integrations/microsoft/provider.js +++ b/server/services/integrations/microsoft/provider.js @@ -202,19 +202,83 @@ function graphUrl(path, query) { return url.toString(); } -async function graphRequest(credentials, { method = 'GET', path, query, body }) { - return fetchJson( +function expiresAtFromSeconds(expiresIn) { + return new Date( + Date.now() + Math.max(1, Number(expiresIn) || 3600) * 1000, + ).toISOString(); +} + +function tokenExpiresSoon(credentials) { + const expiresAt = Date.parse(String(credentials?.expires_at || '')); + return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 60 * 1000; +} + +async function refreshMicrosoftCredentials(credentials, signal) { + const refreshToken = String(credentials?.refresh_token || '').trim(); + if (!refreshToken) { + throw new Error( + 'Microsoft 365 refresh token is missing. Reconnect this integration account.', + ); + } + const { config, tokenUrl } = getMicrosoftEndpoints(); + const token = await fetchJson( + tokenUrl, + { + method: 'POST', + form: { + client_id: config.clientId, + client_secret: config.clientSecret, + grant_type: 'refresh_token', + refresh_token: refreshToken, + scope: credentials.scope || undefined, + }, + signal, + }, + { serviceName: 'Microsoft 365 token refresh' }, + ); + if (!String(token?.access_token || '').trim()) { + throw new Error('Microsoft 365 token refresh did not return an access token.'); + } + return { + ...credentials, + access_token: token.access_token, + refresh_token: token.refresh_token || refreshToken, + expires_in: token.expires_in, + expires_at: expiresAtFromSeconds(token.expires_in), + scope: token.scope || credentials.scope, + token_type: token.token_type || credentials.token_type, + }; +} + +async function graphRequest(context, { method = 'GET', path, query, body }) { + const { signal } = context; + let credentials = context.credentials; + if (tokenExpiresSoon(credentials)) { + credentials = await refreshMicrosoftCredentials(credentials, signal); + context.updateCredentials(credentials); + } + const performRequest = (activeCredentials) => fetchJson( graphUrl(path, query), { method: String(method || 'GET').toUpperCase(), - headers: { Authorization: `Bearer ${credentials.access_token}` }, + headers: { Authorization: `Bearer ${activeCredentials.access_token}` }, ...(body === undefined ? {} : { json: body }), + signal, }, { serviceName: 'Microsoft Graph' }, ); + + try { + return await performRequest(credentials); + } catch (error) { + if (error?.status !== 401 || !credentials.refresh_token) throw error; + credentials = await refreshMicrosoftCredentials(credentials, signal); + context.updateCredentials(credentials); + return performRequest(credentials); + } } -async function executeMicrosoftTool(toolName, args, { credentials }) { +async function executeMicrosoftTool(toolName, args, context) { switch (toolName) { case 'microsoft_365_outlook_list_messages': { const folder = String(args.folder_id || 'inbox').trim(); @@ -222,7 +286,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { ? '/v1.0/me/mailFolders/inbox/messages' : `/v1.0/me/mailFolders/${encodeURIComponent(folder)}/messages`; return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { path, query: { '$top': Math.max(1, Math.min(Number(args.top) || 10, 50)), @@ -233,7 +297,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { }; } case 'microsoft_365_outlook_send_mail': - await graphRequest(credentials, { + await graphRequest(context, { method: 'POST', path: '/v1.0/me/sendMail', body: { @@ -255,7 +319,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { case 'microsoft_365_calendar_list_events': { const hasWindow = args.start && args.end; return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { path: hasWindow ? '/v1.0/me/calendarView' : '/v1.0/me/events', query: { '$top': Math.max(1, Math.min(Number(args.top) || 10, 50)), @@ -268,7 +332,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { } case 'microsoft_365_calendar_create_event': return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { method: 'POST', path: '/v1.0/me/events', body: { @@ -294,7 +358,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { }; case 'microsoft_365_onedrive_list_children': return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { path: args.item_id ? `/v1.0/me/drive/items/${encodeURIComponent(String(args.item_id))}/children` : '/v1.0/me/drive/root/children', @@ -303,14 +367,14 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { }; case 'microsoft_365_teams_list_chats': return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { path: '/v1.0/me/chats', query: { '$top': Math.max(1, Math.min(Number(args.top) || 20, 50)) }, }), }; case 'microsoft_365_teams_send_chat_message': return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { method: 'POST', path: `/v1.0/chats/${encodeURIComponent(requireText(args.chat_id, 'chat_id'))}/messages`, body: { @@ -327,7 +391,7 @@ async function executeMicrosoftTool(toolName, args, { credentials }) { throw new Error('microsoft_365_*_graph_request tools are disabled by default. Set NEOAGENT_ENABLE_MICROSOFT_DYNAMIC_GRAPH_REQUEST=true to enable them.'); } return { - result: await graphRequest(credentials, { + result: await graphRequest(context, { method: args.method, path: requireText(args.path, 'path'), query: args.query, @@ -371,7 +435,7 @@ function createMicrosoftProvider() { appId: app.id, }; }, - async finishOAuth({ code, app }) { + async finishOAuth({ code, app, signal }) { const { config, tokenUrl } = getMicrosoftEndpoints(); const token = await fetchJson( tokenUrl, @@ -385,6 +449,7 @@ function createMicrosoftProvider() { redirect_uri: config.redirectUri, scope: escapeScope(app.scopes), }, + signal, }, { serviceName: 'Microsoft 365' }, ); @@ -395,6 +460,7 @@ function createMicrosoftProvider() { headers: { Authorization: `Bearer ${token.access_token}`, }, + signal, }, { serviceName: 'Microsoft 365' }, ); @@ -413,6 +479,7 @@ function createMicrosoftProvider() { access_token: token.access_token, refresh_token: token.refresh_token, expires_in: token.expires_in, + expires_at: expiresAtFromSeconds(token.expires_in), scope: token.scope, token_type: token.token_type, }, diff --git a/server/services/integrations/neoarchive/provider.js b/server/services/integrations/neoarchive/provider.js index 29111248..caf25598 100644 --- a/server/services/integrations/neoarchive/provider.js +++ b/server/services/integrations/neoarchive/provider.js @@ -13,9 +13,11 @@ const { const { getConnectionAccessMode } = require("../access"); const { decryptValue } = require("../secrets"); const { appendQuery, fetchJson } = require("../oauth_provider"); +const { fetchResponseText } = require("../http"); const { resolvePublicBaseUrl } = require("../env"); const KEY = "neoarchive"; +const MAX_UPLOAD_BYTES = 100 * 1024 * 1024; const APP = Object.freeze({ id: "archive", label: "Archive", @@ -277,24 +279,25 @@ function snapshot(provider, rows, context) { connectionMethod: "oauth", }; } -async function bootstrap(baseUrl) { +async function bootstrap(baseUrl, options = {}) { return fetchJson( `${baseUrl}/api/oauth/companion/neoagent/bootstrap`, { method: "POST", json: { redirectUri: callbackUrl(), appName: "NeoAgent" }, + signal: options.signal, }, { serviceName: "NeoArchive companion bootstrap" }, ); } -async function token(baseUrl, form) { +async function token(baseUrl, form, options = {}) { return fetchJson( `${baseUrl}/oauth/token`, - { method: "POST", form }, + { method: "POST", form, signal: options.signal }, { serviceName: "NeoArchive OAuth token" }, ); } -async function access(connection) { +async function access(connection, options = {}) { const saved = credentials(connection); const baseUrl = normalizeBaseUrl(saved.baseUrl); if (saved.access_token && Number(saved.expires_at_ms) > Date.now() + 60000) @@ -307,7 +310,7 @@ async function access(connection) { grant_type: "refresh_token", client_id: saved.client_id, refresh_token: saved.refresh_token, - }); + }, options); const next = { ...saved, access_token: text(refreshed.access_token), @@ -319,13 +322,14 @@ async function access(connection) { return { baseUrl, accessToken: next.access_token, credentials: next }; } async function request(connection, apiPath, options = {}) { - const auth = await access(connection); + const auth = await access(connection, options); const response = await fetchJson( `${auth.baseUrl}${apiPath}`, { method: options.method || "GET", headers: { Authorization: `Bearer ${auth.accessToken}` }, ...(options.json === undefined ? {} : { json: options.json }), + signal: options.signal, }, { serviceName: "NeoArchive API" }, ); @@ -336,7 +340,7 @@ function required(value, name) { if (!normalized) throw new Error(`${name} is required.`); return normalized; } -async function upload(connection, filePath) { +async function upload(connection, filePath, options = {}) { const raw = text(filePath); if (!raw || raw.split(/[\\/]+/).includes("..")) throw new Error( @@ -346,27 +350,40 @@ async function upload(connection, filePath) { const stats = await fs.promises.stat(resolved); if (!stats.isFile()) throw new Error("file_path must point to a readable file."); + if (stats.size > MAX_UPLOAD_BYTES) + throw new Error(`file_path exceeds the ${MAX_UPLOAD_BYTES}-byte upload limit.`); await fs.promises.access(resolved, fs.constants.R_OK); - const auth = await access(connection); + const auth = await access(connection, options); const form = new FormData(); form.append( "files", - new Blob([await fs.promises.readFile(resolved)]), + new Blob([await fs.promises.readFile(resolved, { signal: options.signal })]), path.basename(resolved), ); - const response = await fetch(`${auth.baseUrl}/api/v1/documents`, { - method: "POST", - headers: { Authorization: `Bearer ${auth.accessToken}` }, - body: form, - }); - const result = await response.json().catch(() => null); + const { response, text: responseText } = await fetchResponseText( + `${auth.baseUrl}/api/v1/documents`, + { + method: "POST", + headers: { Authorization: `Bearer ${auth.accessToken}` }, + body: form, + signal: options.signal, + timeoutMs: 120000, + }, + { serviceName: "NeoArchive upload" }, + ); + let result = null; + try { + result = responseText ? JSON.parse(responseText) : null; + } catch { + result = null; + } if (!response.ok) throw new Error( `NeoArchive upload request failed: ${result?.error || response.statusText}`, ); return { result, credentials: auth.credentials }; } -async function execute(toolName, args, connection) { +async function execute(toolName, args, connection, options = {}) { let pathName; let method = "GET"; let json; @@ -401,7 +418,7 @@ async function execute(toolName, args, connection) { pathName = "/api/v1/document-types"; break; case "neoarchive_upload_document": - return upload(connection, args.file_path); + return upload(connection, args.file_path, options); case "neoarchive_update_document": pathName = `/api/v1/documents/${encodeURIComponent(required(args.document_id, "document_id"))}`; method = "PATCH"; @@ -426,7 +443,11 @@ async function execute(toolName, args, connection) { default: throw new Error(`Unsupported NeoArchive tool: ${toolName}`); } - const result = await request(connection, pathName, { method, json }); + const result = await request(connection, pathName, { + method, + json, + signal: options.signal, + }); return { result: result.response, credentials: result.credentials }; } @@ -483,7 +504,7 @@ function createNeoArchiveProvider() { ? "NeoArchive: setup is ready, but no archive account is connected." : "NeoArchive: connected with document search, metadata, text, upload, archive, and reprocessing tools."; }, - async beginOAuth({ state, codeVerifier, userId, agentId, appKey }) { + async beginOAuth({ state, codeVerifier, userId, agentId, appKey, signal }) { if (text(appKey) !== APP.id) throw new Error("Unknown NeoArchive app."); const stored = config( getProviderConfig( @@ -493,7 +514,7 @@ function createNeoArchiveProvider() { ), ); const baseUrl = normalizeBaseUrl(stored.baseUrl); - const boot = await bootstrap(baseUrl); + const boot = await bootstrap(baseUrl, { signal }); const challenge = crypto .createHash("sha256") .update(String(codeVerifier)) @@ -515,7 +536,7 @@ function createNeoArchiveProvider() { ), }; }, - async finishOAuth({ userId, agentId, code, codeVerifier }) { + async finishOAuth({ userId, agentId, code, codeVerifier, signal }) { const stored = config( getProviderConfig( Number(userId), @@ -524,21 +545,24 @@ function createNeoArchiveProvider() { ), ); const baseUrl = normalizeBaseUrl(stored.baseUrl); - const boot = await bootstrap(baseUrl); + const boot = await bootstrap(baseUrl, { signal }); const issued = await token(baseUrl, { grant_type: "authorization_code", client_id: boot.clientId, code: text(code), redirect_uri: text(boot.redirectUri) || callbackUrl(), code_verifier: text(codeVerifier), - }); + }, { signal }); const accessToken = text(issued.access_token); const refreshToken = text(issued.refresh_token); if (!accessToken || !refreshToken) throw new Error("NeoArchive did not return durable OAuth credentials."); const info = await fetchJson( `${baseUrl}/oauth/userinfo`, - { headers: { Authorization: `Bearer ${accessToken}` } }, + { + headers: { Authorization: `Bearer ${accessToken}` }, + signal, + }, { serviceName: "NeoArchive userinfo" }, ); const host = new URL(baseUrl).host; @@ -565,8 +589,10 @@ function createNeoArchiveProvider() { }, }; }, - async executeTool(toolName, args, connection) { - return execute(toolName, args || {}, connection); + async executeTool(toolName, args, connection, executionOptions = {}) { + return execute(toolName, args || {}, connection, { + signal: executionOptions.signal || null, + }); }, getUserConfig({ userId, agentId }) { const scoped = resolveAgentId(Number(userId), agentId); @@ -584,17 +610,17 @@ function createNeoArchiveProvider() { hasConnectedAccount: accountCount > 0, }; }, - async saveUserConfig({ userId, agentId, config: input }) { + async saveUserConfig({ userId, agentId, config: input, signal }) { const scoped = resolveAgentId(Number(userId), agentId); const existing = config(getProviderConfig(Number(userId), KEY, scoped)); const parsed = config(input, existing); const baseUrl = normalizeBaseUrl(parsed.baseUrl); await fetchJson( `${baseUrl}/api/v1/health`, - { method: "GET" }, + { method: "GET", signal }, { serviceName: "NeoArchive health check" }, ); - await bootstrap(baseUrl); + await bootstrap(baseUrl, { signal }); setProviderConfig(Number(userId), KEY, { baseUrl }, scoped); if (existing.baseUrl && existing.baseUrl !== baseUrl) db.prepare( diff --git a/server/services/integrations/neorecall/client.js b/server/services/integrations/neorecall/client.js index 78bf6c92..d86047e7 100644 --- a/server/services/integrations/neorecall/client.js +++ b/server/services/integrations/neorecall/client.js @@ -29,17 +29,22 @@ function normalizeBaseUrl(value) { return url.toString().replace(/\/+$/, ''); } -async function bootstrap(baseUrl, callbackUrl) { +async function bootstrap(baseUrl, callbackUrl, options = {}) { return fetchJson(`${baseUrl}/api/oauth/companion/neoagent/bootstrap`, { method: 'POST', json: { redirectUri: callbackUrl, appName: 'NeoAgent' }, + signal: options.signal, }, { serviceName: 'NeoRecall companion bootstrap' }); } -async function token(baseUrl, form) { - return fetchJson(`${baseUrl}/oauth/token`, { method: 'POST', form }, { serviceName: 'NeoRecall OAuth token' }); +async function token(baseUrl, form, options = {}) { + return fetchJson( + `${baseUrl}/oauth/token`, + { method: 'POST', form, signal: options.signal }, + { serviceName: 'NeoRecall OAuth token' }, + ); } -async function revoke(credentials) { +async function revoke(credentials, options = {}) { const saved = credentials && typeof credentials === 'object' ? credentials : {}; const baseUrl = normalizeBaseUrl(saved.baseUrl); const clientId = text(saved.client_id); @@ -47,12 +52,13 @@ async function revoke(credentials) { const tokens = [saved.refresh_token, saved.access_token].map(text).filter(Boolean); const outcomes = await Promise.allSettled(tokens.map((value) => fetchJson(`${baseUrl}/oauth/revoke`, { method: 'POST', form: { client_id: clientId, token: value }, + signal: options.signal, }, { serviceName: 'NeoRecall OAuth revocation' }))); const failed = outcomes.find((outcome) => outcome.status === 'rejected'); if (failed) throw failed.reason; } -async function authenticated(credentials) { +async function authenticated(credentials, options = {}) { const saved = credentials && typeof credentials === 'object' ? credentials : {}; const baseUrl = normalizeBaseUrl(saved.baseUrl); if (text(saved.access_token) && Number(saved.expires_at_ms) > Date.now() + 60_000) { @@ -63,7 +69,7 @@ async function authenticated(credentials) { } const refreshed = await token(baseUrl, { grant_type: 'refresh_token', client_id: saved.client_id, refresh_token: saved.refresh_token, - }); + }, options); const next = { ...saved, access_token: text(refreshed.access_token), @@ -97,15 +103,16 @@ function appendApiQuery(path, query) { return encoded ? `${path}?${encoded}` : path; } -async function request(credentials, apiPath) { - const authorization = await authenticated(credentials); +async function request(credentials, apiPath, options = {}) { + const authorization = await authenticated(credentials, options); const response = await fetchJson(`${authorization.baseUrl}${apiPath}`, { headers: { Authorization: `Bearer ${authorization.accessToken}` }, + signal: options.signal, }, { serviceName: 'NeoRecall API' }); return { result: response, credentials: authorization.credentials }; } -async function executeTool(toolName, args, credentials) { +async function executeTool(toolName, args, credentials, options = {}) { let apiPath; switch (toolName) { case 'neorecall_search': { @@ -134,7 +141,7 @@ async function executeTool(toolName, args, credentials) { default: throw new Error(`Unsupported NeoRecall tool: ${toolName}`); } - return request(credentials, apiPath); + return request(credentials, apiPath, options); } module.exports = { bootstrap, executeTool, normalizeBaseUrl, revoke, text, token }; diff --git a/server/services/integrations/neorecall/provider.js b/server/services/integrations/neorecall/provider.js index 34948612..36e0bffc 100644 --- a/server/services/integrations/neorecall/provider.js +++ b/server/services/integrations/neorecall/provider.js @@ -71,9 +71,9 @@ function createNeoRecallProvider() { connectionMethod: 'user_config', requiresRefreshToken: true, getEnvStatus: envStatus, - async beginOAuth({ state, codeVerifier, userId, agentId }) { + async beginOAuth({ state, codeVerifier, userId, agentId, signal }) { const baseUrl = normalizeBaseUrl(storedConfig(userId, resolveAgentId(userId, agentId)).baseUrl); - const boot = await bootstrap(baseUrl, callbackUrl()); + const boot = await bootstrap(baseUrl, callbackUrl(), { signal }); const challenge = crypto.createHash('sha256').update(String(codeVerifier)).digest('base64url'); return { url: appendQuery(text(boot.authorizationEndpoint) || `${baseUrl}/oauth/authorize`, { @@ -83,18 +83,19 @@ function createNeoRecallProvider() { }), }; }, - async finishOAuth({ userId, agentId, code, codeVerifier }) { + async finishOAuth({ userId, agentId, code, codeVerifier, signal }) { const baseUrl = normalizeBaseUrl(storedConfig(userId, resolveAgentId(userId, agentId)).baseUrl); - const boot = await bootstrap(baseUrl, callbackUrl()); + const boot = await bootstrap(baseUrl, callbackUrl(), { signal }); const issued = await token(baseUrl, { grant_type: 'authorization_code', client_id: boot.clientId, code: text(code), redirect_uri: text(boot.redirectUri) || callbackUrl(), code_verifier: text(codeVerifier), - }); + }, { signal }); const accessToken = text(issued.access_token); const refreshToken = text(issued.refresh_token); if (!accessToken || !refreshToken) throw new Error('NeoRecall did not return durable OAuth credentials.'); const info = await fetchJson(`${baseUrl}/oauth/userinfo`, { headers: { Authorization: `Bearer ${accessToken}` }, + signal, }, { serviceName: 'NeoRecall userinfo' }); const host = new URL(baseUrl).host; const accountEmail = text(info.email) || text(info.preferred_username) || `neorecall:${text(info.sub) || host}`; @@ -109,10 +110,14 @@ function createNeoRecallProvider() { }; }, executeTool(toolName, args, context) { - return executeTool(toolName, args || {}, context.credentials); + return executeTool(toolName, args || {}, context.credentials, { + signal: context.signal, + }); }, - disconnect(connection) { - return revoke(connectionCredentials(connection)); + disconnect(connection, executionOptions = {}) { + return revoke(connectionCredentials(connection), { + signal: executionOptions.signal || null, + }); }, }); @@ -122,16 +127,20 @@ function createNeoRecallProvider() { const accountCount = connectedAccountCount(Number(userId), scoped); return { baseUrl: stored.baseUrl, configured: Boolean(stored.baseUrl), accountCount, hasConnectedAccount: accountCount > 0 }; }; - provider.saveUserConfig = async ({ userId, agentId, config }) => { + provider.saveUserConfig = async ({ userId, agentId, config, signal }) => { const normalizedUserId = Number(userId); const scoped = resolveAgentId(normalizedUserId, agentId); const existing = storedConfig(normalizedUserId, scoped); const baseUrl = normalizeBaseUrl(parseConfig(config, existing).baseUrl); - const health = await fetchJson(`${baseUrl}/health`, { method: 'GET' }, { serviceName: 'NeoRecall health check' }); + const health = await fetchJson( + `${baseUrl}/health`, + { method: 'GET', signal }, + { serviceName: 'NeoRecall health check' }, + ); if (text(health?.status) !== 'ok' || text(health?.process) !== 'http') { throw new Error('The configured endpoint did not identify itself as a healthy NeoRecall HTTP service.'); } - const boot = await bootstrap(baseUrl, callbackUrl()); + const boot = await bootstrap(baseUrl, callbackUrl(), { signal }); if (text(boot?.companion) !== 'neoagent' || !text(boot?.clientId)) { throw new Error('The NeoRecall server does not expose the NeoAgent companion OAuth contract.'); } diff --git a/server/services/integrations/notion/provider.js b/server/services/integrations/notion/provider.js index 04e79ee8..a035bb83 100644 --- a/server/services/integrations/notion/provider.js +++ b/server/services/integrations/notion/provider.js @@ -187,7 +187,8 @@ function notionUrl(path, query) { return url.toString(); } -async function notionRequest(credentials, { method = 'GET', path, query, body }) { +async function notionRequest(context, { method = 'GET', path, query, body }) { + const { credentials, signal } = context; return fetchJson( notionUrl(path, query), { @@ -197,16 +198,17 @@ async function notionRequest(credentials, { method = 'GET', path, query, body }) 'Notion-Version': NOTION_VERSION, }, ...(body === undefined ? {} : { json: body }), + signal, }, { serviceName: 'Notion' }, ); } -async function executeNotionTool(toolName, args, { credentials }) { +async function executeNotionTool(toolName, args, context) { switch (toolName) { case 'notion_search': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'POST', path: '/v1/search', body: { @@ -218,13 +220,13 @@ async function executeNotionTool(toolName, args, { credentials }) { }; case 'notion_get_page': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { path: `/v1/pages/${encodeURIComponent(requireText(args.page_id, 'page_id'))}`, }), }; case 'notion_create_page': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'POST', path: '/v1/pages', body: { @@ -236,7 +238,7 @@ async function executeNotionTool(toolName, args, { credentials }) { }; case 'notion_update_page': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'PATCH', path: `/v1/pages/${encodeURIComponent(requireText(args.page_id, 'page_id'))}`, body: { @@ -249,7 +251,7 @@ async function executeNotionTool(toolName, args, { credentials }) { }; case 'notion_query_database': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'POST', path: `/v1/databases/${encodeURIComponent(requireText(args.database_id, 'database_id'))}/query`, body: { @@ -261,14 +263,14 @@ async function executeNotionTool(toolName, args, { credentials }) { }; case 'notion_get_block_children': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}/children`, query: { page_size: Math.max(1, Math.min(Number(args.page_size) || 25, 100)) }, }), }; case 'notion_append_block_children': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'PATCH', path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}/children`, body: { children: Array.isArray(args.children) ? args.children : [] }, @@ -276,7 +278,7 @@ async function executeNotionTool(toolName, args, { credentials }) { }; case 'notion_update_block': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'PATCH', path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}`, body: args.body || {}, @@ -284,14 +286,14 @@ async function executeNotionTool(toolName, args, { credentials }) { }; case 'notion_delete_block': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: 'DELETE', path: `/v1/blocks/${encodeURIComponent(requireText(args.block_id, 'block_id'))}`, }), }; case 'notion_api_request': return { - result: await notionRequest(credentials, { + result: await notionRequest(context, { method: args.method, path: requireText(args.path, 'path'), query: args.query, @@ -332,7 +334,7 @@ function createNotionProvider() { appId: app.id, }; }, - async finishOAuth({ code, app }) { + async finishOAuth({ code, app, signal }) { const config = resolveNotionOAuthConfig(); const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString( 'base64', @@ -349,6 +351,7 @@ function createNotionProvider() { code, redirect_uri: config.redirectUri, }, + signal, }, { serviceName: 'Notion' }, ); diff --git a/server/services/integrations/oauth_provider.js b/server/services/integrations/oauth_provider.js index 6006a4b5..7be08bc8 100644 --- a/server/services/integrations/oauth_provider.js +++ b/server/services/integrations/oauth_provider.js @@ -2,9 +2,19 @@ const { decryptValue } = require('./secrets'); const { getConnectionAccessMode } = require('./access'); +const { fetchResponseText } = require('./http'); +const { isAbortError } = require('../../utils/abort'); +const { + abortableDelay, + computeBackoffMs, + getHttpStatus, + isTransientIoError, + retryAfterMilliseconds, +} = require('../../utils/retry'); -const DEFAULT_OAUTH_HTTP_TIMEOUT_MS = 15000; const OAUTH_STATE_PATTERN = /^[a-f0-9]{32,128}$/i; +const RETRYABLE_INTEGRATION_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']); +const MAX_INTEGRATION_RETRY_DELAY_MS = 10000; function escapeScope(scopes) { return Array.from(new Set((scopes || []).filter(Boolean))).join(' '); @@ -21,6 +31,17 @@ function appendQuery(url, params) { return resolved.toString(); } +function sanitizeRemoteError(value) { + return String(value || '') + .slice(0, 1000) + .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, '$1 [redacted]') + .replace( + /\b(access_token|refresh_token|client_secret|authorization|token)\b\s*[:=]\s*["']?[^\s,"'}\]]+/gi, + '$1=[redacted]', + ) + .trim(); +} + async function fetchJson(url, options = {}, context = {}) { const headers = { Accept: 'application/json', @@ -42,54 +63,68 @@ async function fetchJson(url, options = {}, context = {}) { ).toString(); } - const controller = new AbortController(); - const timeoutMs = Number(options.timeoutMs) > 0 - ? Number(options.timeoutMs) - : DEFAULT_OAUTH_HTTP_TIMEOUT_MS; - const timer = setTimeout(() => controller.abort(), timeoutMs); + const method = String(options.method || 'GET').toUpperCase(); + const maxAttempts = + options.retry === false || !RETRYABLE_INTEGRATION_METHODS.has(method) + ? 1 + : 2; + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + const { response, text } = await fetchResponseText( + url, + { + method, + headers, + body, + signal: options.signal, + timeoutMs: options.timeoutMs, + maxResponseBytes: options.maxResponseBytes, + }, + context, + ); - let response; - try { - response = await fetch(url, { - method: options.method || 'GET', - headers, - body, - signal: controller.signal, - }); - } catch (error) { - if (error?.name === 'AbortError') { - const label = String(context.serviceName || 'OAuth provider').trim() || 'OAuth provider'; - throw new Error(`${label} request timed out after ${timeoutMs}ms.`); - } - throw error; - } finally { - clearTimeout(timer); - } + let data = null; + try { + data = text ? JSON.parse(text) : null; + } catch { + data = null; + } - const text = await response.text(); - let data = null; - try { - data = text ? JSON.parse(text) : null; - } catch { - data = null; - } + const notOkSlackStyle = + data && + typeof data === 'object' && + Object.prototype.hasOwnProperty.call(data, 'ok') && + data.ok === false; - const notOkSlackStyle = - data && - typeof data === 'object' && - Object.prototype.hasOwnProperty.call(data, 'ok') && - data.ok === false; + if (!response.ok || notOkSlackStyle) { + const label = String(context.serviceName || 'OAuth provider').trim() || 'OAuth provider'; + const rawMessage = + (data && (data.error_description || data.error || data.message)) || + text || + `${response.status} ${response.statusText}`; + const error = new Error( + `${label} request failed: ${sanitizeRemoteError(rawMessage)}`, + ); + error.status = response.status; + error.data = data; + error.headers = response.headers; + throw error; + } - if (!response.ok || notOkSlackStyle) { - const label = String(context.serviceName || 'OAuth provider').trim() || 'OAuth provider'; - const message = - (data && (data.error_description || data.error || data.message)) || - text || - `${response.status} ${response.statusText}`; - throw new Error(`${label} request failed: ${String(message).trim()}`); + return data; + } catch (error) { + if (isAbortError(error, options.signal) || attempt >= maxAttempts) throw error; + const retryAfterMs = retryAfterMilliseconds(error.headers); + const rateLimited = + (getHttpStatus(error) === 403 && retryAfterMs !== null) + || String(error.data?.error || '').toLowerCase() === 'ratelimited'; + if (!rateLimited && !isTransientIoError(error)) throw error; + const delayMs = retryAfterMs ?? computeBackoffMs(attempt, 250, 2000); + if (delayMs > MAX_INTEGRATION_RETRY_DELAY_MS) throw error; + await abortableDelay(delayMs, options.signal); + } } - - return data; + throw new Error('Integration request exhausted its retry attempts.'); } function sortConnections(rows) { @@ -318,7 +353,7 @@ function createOAuthProvider(options = {}) { connectionMethod: this.connectionMethod, }; }, - async beginOAuth({ state, codeVerifier, appKey, userId, agentId }) { + async beginOAuth({ state, codeVerifier, appKey, userId, agentId, signal }) { const app = getApp(appKey); if (!app) { throw new Error(`Unknown ${this.label} app: ${appKey}`); @@ -335,9 +370,18 @@ function createOAuthProvider(options = {}) { agentId, app, env, + signal, }); }, - async finishOAuth({ code, codeVerifier, appKey, userId, agentId, state }) { + async finishOAuth({ + code, + codeVerifier, + appKey, + userId, + agentId, + state, + signal, + }) { const app = getApp(appKey); if (!app) { throw new Error(`Unknown ${this.label} app: ${appKey}`); @@ -351,15 +395,16 @@ function createOAuthProvider(options = {}) { state: normalizedState, app, env: this.getEnvStatus({ userId, agentId }), + signal, }); }, - async disconnect(connectionRow) { + async disconnect(connectionRow, executionOptions = {}) { if (typeof options.disconnect === 'function') { - return options.disconnect(connectionRow); + return options.disconnect(connectionRow, executionOptions); } return null; }, - async executeTool(toolName, args, connectionRow) { + async executeTool(toolName, args, connectionRow, executionOptions = {}) { if (typeof options.executeTool !== 'function') { return null; } @@ -371,7 +416,8 @@ function createOAuthProvider(options = {}) { } catch { credentials = {}; } - return options.executeTool(toolName, args, { + let credentialsUpdated = false; + const integrationContext = { appId: toolAppMap.get(String(toolName || '').trim()) || connectionRow.app_key, connection: connectionRow, credentials, @@ -379,7 +425,25 @@ function createOAuthProvider(options = {}) { userId: connectionRow?.user_id, agentId: connectionRow?.agent_id, }), - }); + signal: executionOptions.signal || null, + updateCredentials(nextCredentials) { + if (!nextCredentials || typeof nextCredentials !== 'object') return; + integrationContext.credentials = nextCredentials; + credentialsUpdated = true; + }, + }; + const execution = await options.executeTool( + toolName, + args, + integrationContext, + ); + if (!execution || execution.credentials || !credentialsUpdated) { + return execution; + } + return { + ...execution, + credentials: integrationContext.credentials, + }; }, summarizeConnection(connectionRows) { const snapshot = this.buildSnapshot(connectionRows); diff --git a/server/services/integrations/slack/provider.js b/server/services/integrations/slack/provider.js index d13150e1..414062b1 100644 --- a/server/services/integrations/slack/provider.js +++ b/server/services/integrations/slack/provider.js @@ -130,7 +130,11 @@ function requireText(value, label) { return text; } -async function slackApi(credentials, methodName, { httpMethod = 'POST', query, body } = {}) { +async function slackApi( + credentials, + methodName, + { httpMethod = 'POST', query, body, signal } = {}, +) { const normalizedMethod = requireText(methodName, 'method_name'); if (!/^[a-zA-Z0-9_.]+$/.test(normalizedMethod)) { throw new Error('method_name must be a Slack Web API method name.'); @@ -149,16 +153,92 @@ async function slackApi(credentials, methodName, { httpMethod = 'POST', query, b method: normalizedHttpMethod, headers: { Authorization: `Bearer ${credentials.access_token}` }, ...(body === undefined ? {} : { json: body }), + signal, }, { serviceName: 'Slack' }, ); } -async function executeSlackTool(toolName, args, { credentials }) { +function expiresAtFromSeconds(expiresIn) { + const seconds = Number(expiresIn); + if (!Number.isFinite(seconds) || seconds <= 0) return null; + return new Date(Date.now() + seconds * 1000).toISOString(); +} + +function tokenExpiresSoon(credentials) { + const expiresAt = Date.parse(String(credentials?.expires_at || '')); + return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 60 * 1000; +} + +async function refreshSlackCredentials(credentials, signal) { + const refreshToken = String(credentials?.refresh_token || '').trim(); + if (!refreshToken) { + throw new Error('Slack refresh token is missing. Reconnect this integration account.'); + } + const config = resolveSlackOAuthConfig(); + const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64'); + const token = await fetchJson( + 'https://slack.com/api/oauth.v2.access', + { + method: 'POST', + headers: { Authorization: `Basic ${basic}` }, + form: { + grant_type: 'refresh_token', + refresh_token: refreshToken, + }, + signal, + }, + { serviceName: 'Slack token refresh' }, + ); + const userToken = token?.authed_user?.access_token || token.access_token; + if (!userToken) { + throw new Error('Slack token refresh did not return an access token.'); + } + const nextRefreshToken = + token?.authed_user?.refresh_token || token.refresh_token || refreshToken; + const expiresIn = token?.authed_user?.expires_in || token.expires_in; + return { + ...credentials, + access_token: userToken, + refresh_token: nextRefreshToken, + expires_in: expiresIn, + expires_at: expiresAtFromSeconds(expiresIn), + bot_access_token: token.access_token || credentials.bot_access_token, + token_type: token?.authed_user?.token_type || token.token_type || credentials.token_type, + team: token.team || credentials.team || null, + enterprise: token.enterprise || credentials.enterprise || null, + authed_user: token.authed_user + ? { ...(credentials.authed_user || {}), ...token.authed_user } + : credentials.authed_user || null, + }; +} + +async function slackRequest(context, methodName, options = {}) { + const { signal } = context; + let credentials = context.credentials; + if (tokenExpiresSoon(credentials)) { + credentials = await refreshSlackCredentials(credentials, signal); + context.updateCredentials(credentials); + } + + try { + return await slackApi(credentials, methodName, { ...options, signal }); + } catch (error) { + const code = String(error?.data?.error || '').toLowerCase(); + if (!credentials.refresh_token || !['invalid_auth', 'token_expired'].includes(code)) { + throw error; + } + credentials = await refreshSlackCredentials(credentials, signal); + context.updateCredentials(credentials); + return slackApi(credentials, methodName, { ...options, signal }); + } +} + +async function executeSlackTool(toolName, args, context) { switch (toolName) { case 'slack_list_conversations': return { - result: await slackApi(credentials, 'conversations.list', { + result: await slackRequest(context, 'conversations.list', { httpMethod: 'GET', query: { types: String(args.types || 'public_channel,private_channel,im,mpim'), @@ -168,7 +248,7 @@ async function executeSlackTool(toolName, args, { credentials }) { }; case 'slack_get_conversation_history': return { - result: await slackApi(credentials, 'conversations.history', { + result: await slackRequest(context, 'conversations.history', { httpMethod: 'GET', query: { channel: requireText(args.channel, 'channel'), @@ -178,7 +258,7 @@ async function executeSlackTool(toolName, args, { credentials }) { }; case 'slack_post_message': return { - result: await slackApi(credentials, 'chat.postMessage', { + result: await slackRequest(context, 'chat.postMessage', { body: { channel: requireText(args.channel, 'channel'), text: requireText(args.text, 'text'), @@ -188,7 +268,7 @@ async function executeSlackTool(toolName, args, { credentials }) { }; case 'slack_search_messages': return { - result: await slackApi(credentials, 'search.messages', { + result: await slackRequest(context, 'search.messages', { httpMethod: 'GET', query: { query: requireText(args.query, 'query'), @@ -198,7 +278,7 @@ async function executeSlackTool(toolName, args, { credentials }) { }; case 'slack_get_user_info': return { - result: await slackApi(credentials, 'users.info', { + result: await slackRequest(context, 'users.info', { httpMethod: 'GET', query: { user: requireText(args.user, 'user') }, }), @@ -208,7 +288,7 @@ async function executeSlackTool(toolName, args, { credentials }) { throw new Error('slack_api_request is disabled by default. Set NEOAGENT_ENABLE_SLACK_DYNAMIC_API_REQUEST=true to enable it.'); } return { - result: await slackApi(credentials, requireText(args.method_name, 'method_name'), { + result: await slackRequest(context, requireText(args.method_name, 'method_name'), { httpMethod: args.http_method || 'POST', query: args.query, body: args.body, @@ -247,7 +327,7 @@ function createSlackProvider() { appId: app.id, }; }, - async finishOAuth({ code, app }) { + async finishOAuth({ code, app, signal }) { const config = resolveSlackOAuthConfig(); const token = await fetchJson( 'https://slack.com/api/oauth.v2.access', @@ -260,18 +340,24 @@ function createSlackProvider() { grant_type: 'authorization_code', redirect_uri: config.redirectUri, }, + signal, }, { serviceName: 'Slack' }, ); const userToken = token?.authed_user?.access_token || token.access_token; + const refreshToken = + token?.authed_user?.refresh_token || token.refresh_token || null; + const expiresIn = token?.authed_user?.expires_in || token.expires_in || null; const authTest = await slackApi({ access_token: userToken }, 'auth.test', { httpMethod: 'GET', + signal, }); const profile = authTest?.user_id ? await slackApi({ access_token: userToken }, 'users.info', { httpMethod: 'GET', query: { user: authTest.user_id }, + signal, }).catch(() => null) : null; const user = profile?.user || {}; @@ -287,6 +373,9 @@ function createSlackProvider() { accountEmail, credentials: { access_token: userToken, + refresh_token: refreshToken, + expires_in: expiresIn, + expires_at: expiresAtFromSeconds(expiresIn), bot_access_token: token.access_token, token_type: token.token_type, team: token.team || null, diff --git a/server/services/integrations/spotify/provider.js b/server/services/integrations/spotify/provider.js index d7e86219..63137f4d 100644 --- a/server/services/integrations/spotify/provider.js +++ b/server/services/integrations/spotify/provider.js @@ -1,7 +1,8 @@ 'use strict'; const { describeEnvStatus, resolveSpotifyOAuthConfig } = require('../env'); -const { appendQuery, createOAuthProvider } = require('../oauth_provider'); +const { appendQuery, createOAuthProvider, fetchJson } = require('../oauth_provider'); +const { fetchResponseText } = require('../http'); const SPOTIFY_APPS = [ { @@ -121,33 +122,32 @@ function spotifyUrl(path, query) { return url.toString(); } -async function refreshSpotifyAccessToken(config, credentials) { +async function refreshSpotifyAccessToken(config, credentials, signal = null) { const refreshToken = String(credentials?.refresh_token || '').trim(); if (!refreshToken) { return credentials; } const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64'); - const response = await fetch('https://accounts.spotify.com/api/token', { - method: 'POST', - headers: { - Authorization: `Basic ${basic}`, - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: 'application/json', + const data = await fetchJson( + 'https://accounts.spotify.com/api/token', + { + method: 'POST', + headers: { Authorization: `Basic ${basic}` }, + form: { + grant_type: 'refresh_token', + refresh_token: refreshToken, + }, + signal, }, - body: new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: refreshToken, - }).toString(), - }); - const data = await response.json().catch(() => ({})); - if (!response.ok) { - const message = data?.error_description || data?.error || `${response.status} ${response.statusText}`; - throw new Error(`Spotify token refresh failed: ${message}`); + { serviceName: 'Spotify token refresh' }, + ); + if (!String(data?.access_token || '').trim()) { + throw new Error('Spotify token refresh did not return an access token.'); } const expiresIn = Number(data?.expires_in) || 3600; return { ...credentials, - access_token: data?.access_token || credentials.access_token, + access_token: data.access_token, refresh_token: data?.refresh_token || refreshToken, token_type: data?.token_type || credentials.token_type || 'Bearer', scope: data?.scope || credentials.scope || '', @@ -156,7 +156,7 @@ async function refreshSpotifyAccessToken(config, credentials) { }; } -async function ensureSpotifyAccessToken(config, credentials) { +async function ensureSpotifyAccessToken(config, credentials, signal = null) { const accessToken = String(credentials?.access_token || '').trim(); if (!accessToken) { throw new Error('Spotify access token is missing. Reconnect this integration account.'); @@ -165,32 +165,36 @@ async function ensureSpotifyAccessToken(config, credentials) { const expiresAt = Date.parse(String(credentials?.expires_at || '')); const isNearExpiry = Number.isFinite(expiresAt) && expiresAt <= Date.now() + 60 * 1000; if (isNearExpiry && credentials?.refresh_token) { - return refreshSpotifyAccessToken(config, credentials); + return refreshSpotifyAccessToken(config, credentials, signal); } return credentials; } -async function spotifyRequest(config, credentials, { method = 'GET', path, query, body }) { - let nextCredentials = await ensureSpotifyAccessToken(config, credentials); - const tokenType = String(nextCredentials?.token_type || 'Bearer').trim() || 'Bearer'; +async function spotifyRequest(config, context, { method = 'GET', path, query, body }) { + const { credentials, signal } = context; + let nextCredentials = await ensureSpotifyAccessToken(config, credentials, signal); const performRequest = async (tokenCreds) => { const tokenType = String(tokenCreds?.token_type || 'Bearer').trim() || 'Bearer'; - const response = await fetch(spotifyUrl(path, query), { - method: String(method || 'GET').toUpperCase(), - headers: { - Authorization: `${tokenType} ${tokenCreds.access_token}`, - Accept: 'application/json', - ...(body === undefined ? {} : { 'Content-Type': 'application/json' }), + const { response, text } = await fetchResponseText( + spotifyUrl(path, query), + { + method: String(method || 'GET').toUpperCase(), + headers: { + Authorization: `${tokenType} ${tokenCreds.access_token}`, + Accept: 'application/json', + ...(body === undefined ? {} : { 'Content-Type': 'application/json' }), + }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), + signal, }, - ...(body === undefined ? {} : { body: JSON.stringify(body) }), - }); + { serviceName: 'Spotify' }, + ); if (response.status === 204) { return { ok: true, data: null, response }; } - const text = await response.text(); let parsed = null; try { parsed = text ? JSON.parse(text) : null; @@ -202,7 +206,7 @@ async function spotifyRequest(config, credentials, { method = 'GET', path, query let result = await performRequest(nextCredentials); if (!result.ok && result.response.status === 401 && nextCredentials.refresh_token) { - nextCredentials = await refreshSpotifyAccessToken(config, nextCredentials); + nextCredentials = await refreshSpotifyAccessToken(config, nextCredentials, signal); result = await performRequest(nextCredentials); } @@ -219,18 +223,18 @@ async function spotifyRequest(config, credentials, { method = 'GET', path, query }; } -async function executeSpotifyTool(toolName, args, { credentials }) { +async function executeSpotifyTool(toolName, args, context) { const config = resolveSpotifyOAuthConfig(); switch (toolName) { case 'spotify_get_current_playback': { - const { data, credentials: updated } = await spotifyRequest(config, credentials, { + const { data, credentials: updated } = await spotifyRequest(config, context, { path: '/v1/me/player', }); return { result: data || { is_playing: false }, credentials: updated }; } case 'spotify_get_recently_played': { const limit = Math.max(1, Math.min(Number(args.limit) || 20, 50)); - const { data, credentials: updated } = await spotifyRequest(config, credentials, { + const { data, credentials: updated } = await spotifyRequest(config, context, { path: '/v1/me/player/recently-played', query: { limit }, }); @@ -246,7 +250,7 @@ async function executeSpotifyTool(toolName, args, { credentials }) { const type = types.length > 0 ? types.join(',') : 'track'; const limit = Math.max(1, Math.min(Number(args.limit) || 10, 50)); const market = String(args.market || '').trim().toUpperCase(); - const { data, credentials: updated } = await spotifyRequest(config, credentials, { + const { data, credentials: updated } = await spotifyRequest(config, context, { path: '/v1/search', query: { q: queryText, @@ -348,7 +352,7 @@ async function executeSpotifyTool(toolName, args, { credentials }) { throw new Error('Unsupported action. Use play, pause, next, previous, seek, set_volume, shuffle, or repeat.'); } - const { data, credentials: updated } = await spotifyRequest(config, credentials, request); + const { data, credentials: updated } = await spotifyRequest(config, context, request); return { result: { action, @@ -359,7 +363,7 @@ async function executeSpotifyTool(toolName, args, { credentials }) { }; } case 'spotify_api_request': { - const { data, credentials: updated } = await spotifyRequest(config, credentials, { + const { data, credentials: updated } = await spotifyRequest(config, context, { method: args.method, path: requireText(args.path, 'path'), query: args.query, @@ -372,20 +376,16 @@ async function executeSpotifyTool(toolName, args, { credentials }) { } } -async function fetchSpotifyProfile(accessToken) { - const response = await fetch('https://api.spotify.com/v1/me', { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', +async function fetchSpotifyProfile(accessToken, signal = null) { + return fetchJson( + 'https://api.spotify.com/v1/me', + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}` }, + signal, }, - }); - const payload = await response.json().catch(() => ({})); - if (!response.ok) { - const message = payload?.error?.message || payload?.error || `${response.status} ${response.statusText}`; - throw new Error(`Spotify profile request failed: ${message}`); - } - return payload; + { serviceName: 'Spotify profile' }, + ); } function createSpotifyProvider() { @@ -418,27 +418,23 @@ function createSpotifyProvider() { appId: app.id, }; }, - async finishOAuth({ code, app }) { + async finishOAuth({ code, app, signal }) { const config = resolveSpotifyOAuthConfig(); const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64'); - const tokenResponse = await fetch('https://accounts.spotify.com/api/token', { - method: 'POST', - headers: { - Authorization: `Basic ${basic}`, - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: 'application/json', + const token = await fetchJson( + 'https://accounts.spotify.com/api/token', + { + method: 'POST', + headers: { Authorization: `Basic ${basic}` }, + form: { + grant_type: 'authorization_code', + code, + redirect_uri: config.redirectUri, + }, + signal, }, - body: new URLSearchParams({ - grant_type: 'authorization_code', - code, - redirect_uri: config.redirectUri, - }).toString(), - }); - const token = await tokenResponse.json().catch(() => ({})); - if (!tokenResponse.ok) { - const message = token?.error_description || token?.error || `${tokenResponse.status} ${tokenResponse.statusText}`; - throw new Error(`Spotify OAuth token exchange failed: ${message}`); - } + { serviceName: 'Spotify OAuth token exchange' }, + ); const accessToken = String(token?.access_token || '').trim(); if (!accessToken) { @@ -449,7 +445,7 @@ function createSpotifyProvider() { throw new Error('Spotify OAuth did not return a refresh token.'); } - const profile = await fetchSpotifyProfile(accessToken); + const profile = await fetchSpotifyProfile(accessToken, signal); const accountEmail = String(profile?.email || '').trim() || (String(profile?.id || '').trim() ? `spotify:${String(profile.id).trim()}` : 'spotify_user'); diff --git a/server/services/integrations/trello/provider.js b/server/services/integrations/trello/provider.js index 45625be6..a65bc20c 100644 --- a/server/services/integrations/trello/provider.js +++ b/server/services/integrations/trello/provider.js @@ -276,7 +276,10 @@ function trelloUrl(path, query = {}, config = {}) { async function trelloRequest(config, options = {}) { return fetchJson( trelloUrl(options.path, options.query, config), - { method: String(options.method || 'GET').toUpperCase() }, + { + method: String(options.method || 'GET').toUpperCase(), + signal: options.signal || config.signal, + }, { serviceName: 'Trello' }, ); } @@ -492,8 +495,15 @@ function resolveTrelloCredentials(connection, credentials = {}) { return { apiKey, token }; } -async function executeTrelloTool(toolName, args, { connection, credentials }) { - const config = resolveTrelloCredentials(connection, credentials); +async function executeTrelloTool( + toolName, + args, + { connection, credentials, signal }, +) { + const config = { + ...resolveTrelloCredentials(connection, credentials), + signal, + }; switch (toolName) { case 'trello_get_me': { @@ -731,14 +741,18 @@ function createTrelloProvider() { } return 'Trello: native Trello access is connected in this run with one Trello account for board, list, card, comment, and search tools.'; }, - async executeTool(toolName, args, connection) { + async executeTool(toolName, args, connection, executionOptions = {}) { let credentials = {}; try { credentials = JSON.parse(decryptValue(connection.credentials_json || '{}') || '{}'); } catch { credentials = {}; } - return executeTrelloTool(toolName, args, { connection, credentials }); + return executeTrelloTool(toolName, args, { + connection, + credentials, + signal: executionOptions.signal || null, + }); }, getUserConfig({ userId, agentId }) { const normalizedUserId = Number(userId); @@ -757,7 +771,7 @@ function createTrelloProvider() { hasConnectedAccount: accountCount > 0, }; }, - async saveUserConfig({ userId, agentId, config }) { + async saveUserConfig({ userId, agentId, config, signal }) { const normalizedUserId = Number(userId); if (!Number.isInteger(normalizedUserId) || normalizedUserId <= 0) { throw new Error('A valid user is required to save Trello configuration.'); @@ -796,7 +810,7 @@ function createTrelloProvider() { let profile; try { - profile = await fetchTrelloMemberProfile({ apiKey, token }); + profile = await fetchTrelloMemberProfile({ apiKey, token, signal }); } catch (error) { const message = String(error?.message || '').toLowerCase(); if ( diff --git a/server/services/integrations/weather/provider.js b/server/services/integrations/weather/provider.js index ec60b8f7..fc651f0a 100644 --- a/server/services/integrations/weather/provider.js +++ b/server/services/integrations/weather/provider.js @@ -152,14 +152,14 @@ function cleanLocationResult(item = {}) { }; } -async function geocodeLocation(locationQuery, limit = 1) { +async function geocodeLocation(locationQuery, limit = 1, signal = null) { const query = String(locationQuery || '').trim(); if (!query) { throw new Error('location is required when latitude/longitude are not provided.'); } const result = await fetchJson( `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(query)}&count=${Math.max(1, Math.min(Number(limit) || 1, 10))}&language=en&format=json`, - { method: 'GET' }, + { method: 'GET', signal }, { serviceName: 'Open-Meteo geocoding' }, ); const matches = Array.isArray(result?.results) ? result.results : []; @@ -169,7 +169,7 @@ async function geocodeLocation(locationQuery, limit = 1) { return matches.map(cleanLocationResult); } -async function resolveLocation(args = {}, connection = null) { +async function resolveLocation(args = {}, connection = null, signal = null) { const latitude = toNumber(args.latitude, null); const longitude = toNumber(args.longitude, null); if (latitude !== null && longitude !== null) { @@ -183,7 +183,7 @@ async function resolveLocation(args = {}, connection = null) { const location = String(args.location || '').trim(); if (location) { - const [best] = await geocodeLocation(location, 1); + const [best] = await geocodeLocation(location, 1, signal); return { latitude: best.latitude, longitude: best.longitude, @@ -221,7 +221,7 @@ function enrichCurrent(current = {}) { }; } -async function fetchForecastForLocation(location, forecastHours = 24) { +async function fetchForecastForLocation(location, forecastHours = 24, signal = null) { const horizon = Math.max(1, Math.min(Number(forecastHours) || 24, 72)); const url = new URL('https://api.open-meteo.com/v1/forecast'); url.searchParams.set('latitude', String(location.latitude)); @@ -259,7 +259,11 @@ async function fetchForecastForLocation(location, forecastHours = 24) { url.searchParams.set('forecast_hours', String(horizon)); url.searchParams.set('timezone', location.timezone || 'auto'); - const result = await fetchJson(url.toString(), { method: 'GET' }, { serviceName: 'Open-Meteo forecast' }); + const result = await fetchJson( + url.toString(), + { method: 'GET', signal }, + { serviceName: 'Open-Meteo forecast' }, + ); const hourly = result?.hourly || {}; const times = Array.isArray(hourly.time) ? hourly.time : []; const rows = times.map((time, index) => { @@ -304,6 +308,7 @@ class WeatherProvider { this.sessions = new Map(); this.sessionTTL = 30 * 60 * 1000; this.#pruneTimer = setInterval(() => this.pruneExpiredSessions(), this.sessionTTL); + this.#pruneTimer.unref?.(); } #pruneTimer = null; @@ -504,7 +509,8 @@ class WeatherProvider { } } - async executeTool(toolName, args, connection) { + async executeTool(toolName, args, connection, executionOptions = {}) { + const signal = executionOptions.signal || null; switch (toolName) { case 'weather_search_locations': { const query = String(args.query || '').trim(); @@ -512,7 +518,7 @@ class WeatherProvider { throw new Error('query is required.'); } const limit = Math.max(1, Math.min(Number(args.limit) || 5, 10)); - const results = await geocodeLocation(query, limit); + const results = await geocodeLocation(query, limit, signal); return { result: { query, @@ -522,8 +528,8 @@ class WeatherProvider { }; } case 'weather_get_current': { - const location = await resolveLocation(args, connection); - const forecast = await fetchForecastForLocation(location, 1); + const location = await resolveLocation(args, connection, signal); + const forecast = await fetchForecastForLocation(location, 1, signal); return { result: { location: forecast.location, @@ -532,9 +538,9 @@ class WeatherProvider { }; } case 'weather_get_forecast': { - const location = await resolveLocation(args, connection); + const location = await resolveLocation(args, connection, signal); const forecastHours = Math.max(1, Math.min(Number(args.forecast_hours) || 24, 72)); - const forecast = await fetchForecastForLocation(location, forecastHours); + const forecast = await fetchForecastForLocation(location, forecastHours, signal); return { result: { location: forecast.location, diff --git a/server/services/integrations/whatsapp/provider.js b/server/services/integrations/whatsapp/provider.js index 106eb03c..f89e7d7e 100644 --- a/server/services/integrations/whatsapp/provider.js +++ b/server/services/integrations/whatsapp/provider.js @@ -9,6 +9,10 @@ const { AGENT_DATA_DIR } = require('../../../../runtime/paths'); const { encryptValue, decryptValue } = require('../secrets'); const { withConnectionAccessMode } = require('../access'); const { normalizeWhatsAppId, toWhatsAppJid } = require('../../../utils/whatsapp'); +const { + waitForAbortableResult, + waitForBoundedResult, +} = require('../http'); const WHATSAPP_APP = { id: 'personal', @@ -222,6 +226,7 @@ class WhatsAppPersonalProvider extends EventEmitter { this.reconnectTimers = new Map(); this.sessionReconnectTimers = new Map(); this.io = options.io || null; + this.shuttingDown = false; } _clearReconnectTimer(connectionId) { @@ -241,7 +246,7 @@ class WhatsAppPersonalProvider extends EventEmitter { } _scheduleReconnect(client, attempt = 1) { - if (!client?.connectionId || client.manualDisconnect) { + if (this.shuttingDown || !client?.connectionId || client.manualDisconnect) { return; } this._clearReconnectTimer(client.connectionId); @@ -267,11 +272,17 @@ class WhatsAppPersonalProvider extends EventEmitter { } } }, delayMs); + timer.unref?.(); this.reconnectTimers.set(client.connectionId, timer); } _scheduleSessionReconnect(session, attempt = 1) { - if (!session?.id || session.status === 'connected' || session.status === 'logged_out') { + if ( + this.shuttingDown || + !session?.id || + session.status === 'connected' || + session.status === 'logged_out' + ) { return; } this._clearSessionReconnectTimer(session.id); @@ -290,6 +301,7 @@ class WhatsAppPersonalProvider extends EventEmitter { current.error = err?.message || 'connection_failed'; }); }, delayMs); + timer.unref?.(); this.sessionReconnectTimers.set(session.id, timer); } @@ -451,6 +463,9 @@ class WhatsAppPersonalProvider extends EventEmitter { } async beginConnection({ userId, agentId, appKey }) { + if (this.shuttingDown) { + throw new Error('WhatsApp integration is shutting down.'); + } if (!this.getApp(appKey)) { throw new Error(`Unknown ${this.label} app: ${appKey || 'missing app key'}`); } @@ -533,8 +548,9 @@ class WhatsAppPersonalProvider extends EventEmitter { } } - async executeTool(toolName, args, connection) { - const client = await this._ensureClient(connection); + async executeTool(toolName, args, connection, executionOptions = {}) { + const signal = executionOptions.signal || null; + const client = await this._ensureClient(connection, { signal }); switch (toolName) { case 'whatsapp_personal_get_profile': @@ -584,9 +600,16 @@ class WhatsAppPersonalProvider extends EventEmitter { } case 'whatsapp_personal_send_message': { const jid = this._normalizeChatId(requireText(args.to, 'to')); - await client.socket.sendMessage(jid, { - text: requireText(args.text, 'text'), - }); + await waitForBoundedResult( + client.socket.sendMessage(jid, { + text: requireText(args.text, 'text'), + }), + { + signal, + timeoutMs: 60000, + serviceName: 'WhatsApp send', + }, + ); return { result: { sent: true, @@ -900,13 +923,13 @@ class WhatsAppPersonalProvider extends EventEmitter { return row?.id; } - async _ensureClient(connection) { + async _ensureClient(connection, options = {}) { const existing = this.clients.get(connection.id); if (existing?.status === 'connected' && existing.socket) { return existing; } if (existing?.connectPromise) { - await existing.connectPromise; + await waitForAbortableResult(existing.connectPromise, options.signal); const ready = this.clients.get(connection.id); if (ready?.status === 'connected' && ready.socket) { return ready; @@ -935,16 +958,16 @@ class WhatsAppPersonalProvider extends EventEmitter { logger: createSilentLogger(), connectPromise: null, }; - client.connectPromise = this._connectClient(client); + const connectPromise = this._connectClient(client); + client.connectPromise = connectPromise; this.clients.set(connection.id, client); - try { - await client.connectPromise; - } finally { + void connectPromise.finally(() => { const current = this.clients.get(connection.id); - if (current) { + if (current?.connectPromise === connectPromise) { current.connectPromise = null; } - } + }).catch(() => {}); + await waitForAbortableResult(connectPromise, options.signal); const ready = this.clients.get(connection.id); if (!ready?.socket || ready.status !== 'connected') { throw new Error('WhatsApp personal account is not connected.'); @@ -953,6 +976,9 @@ class WhatsAppPersonalProvider extends EventEmitter { } async _connectClient(client) { + if (this.shuttingDown) { + throw new Error('WhatsApp integration is shutting down.'); + } ensureDir(client.authDir); client.manualDisconnect = false; const { @@ -981,6 +1007,9 @@ class WhatsAppPersonalProvider extends EventEmitter { if (Array.isArray(version) && version.length > 0) { socketConfig.version = version; } + try { + client.socket?.end(new Error('client_reconnect')); + } catch {} const sock = makeWASocket(socketConfig); client.socket = sock; @@ -989,6 +1018,10 @@ class WhatsAppPersonalProvider extends EventEmitter { await new Promise((resolve, reject) => { const timeout = setTimeout(() => { + client.status = 'disconnected'; + try { + sock.end(new Error('connection_timeout')); + } catch {} reject(new Error('WhatsApp connection timed out.')); }, 60000); @@ -1017,7 +1050,10 @@ class WhatsAppPersonalProvider extends EventEmitter { async _resolveBaileysVersion(fetchLatestBaileysVersion) { try { - const result = await fetchLatestBaileysVersion(); + const result = await waitForBoundedResult(fetchLatestBaileysVersion(), { + timeoutMs: 15000, + serviceName: 'WhatsApp version discovery', + }); if (Array.isArray(result?.version) && result.version.length > 0) { return result.version; } @@ -1026,6 +1062,30 @@ class WhatsAppPersonalProvider extends EventEmitter { } return null; } + + async shutdown() { + this.shuttingDown = true; + for (const timer of this.reconnectTimers.values()) clearTimeout(timer); + for (const timer of this.sessionReconnectTimers.values()) clearTimeout(timer); + this.reconnectTimers.clear(); + this.sessionReconnectTimers.clear(); + + const sockets = new Set(); + for (const client of this.clients.values()) { + client.manualDisconnect = true; + if (client.socket) sockets.add(client.socket); + } + for (const session of this.sessions.values()) { + if (session.socket) sockets.add(session.socket); + } + for (const socket of sockets) { + try { + socket.end(new Error('server_shutdown')); + } catch {} + } + this.clients.clear(); + this.sessions.clear(); + } } function createWhatsAppPersonalProvider(options = {}) { diff --git a/server/services/manager.js b/server/services/manager.js index 7152a7df..b78d97f2 100644 --- a/server/services/manager.js +++ b/server/services/manager.js @@ -566,7 +566,27 @@ async function stopServices(app) { console.error('[ApprovalGate] Shutdown error:', getErrorMessage(err)); } } - if (app.locals.agentEngine && typeof app.locals.agentEngine.interruptAllActiveRuns === 'function') { + if (app.locals.agentEngine && typeof app.locals.agentEngine.shutdown === 'function') { + try { + tasks.push( + app.locals.agentEngine.shutdown().then((status) => { + if (status.timedOut) { + console.warn( + `[AgentEngine] Shutdown timed out with ${status.pendingCount} operation(s) still settling`, + ); + } else { + logServiceReady('Agent engine shutdown complete'); + } + }), + ); + logServiceReady('Active runs and background work marked interrupted'); + } catch (err) { + console.error('[AgentEngine] Shutdown error:', getErrorMessage(err)); + } + } else if ( + app.locals.agentEngine + && typeof app.locals.agentEngine.interruptAllActiveRuns === 'function' + ) { try { app.locals.agentEngine.interruptAllActiveRuns(); logServiceReady('Active runs marked interrupted'); @@ -595,6 +615,23 @@ async function stopServices(app) { ); } + if (app.locals.messagingAutomationRuntime) { + tasks.push( + Promise.resolve() + .then(() => app.locals.messagingAutomationRuntime.shutdown()) + .then((status) => { + if (status.timedOut) { + console.warn('[MessagingAutomation] Shutdown timed out while work was settling'); + } else { + logServiceReady('Messaging automation shutdown complete'); + } + }) + .catch((err) => { + console.error('[MessagingAutomation] Shutdown error:', getErrorMessage(err)); + }), + ); + } + if (app.locals.streamHub) { try { await app.locals.streamHub.shutdown(); @@ -617,6 +654,22 @@ async function stopServices(app) { ); } + if ( + app.locals.integrationManager && + typeof app.locals.integrationManager.shutdown === 'function' + ) { + tasks.push( + Promise.resolve() + .then(() => app.locals.integrationManager.shutdown()) + .then((status) => { + logServiceReady(`Official integrations shutdown complete (${status.state})`); + }) + .catch((err) => { + console.error('[Integrations] Shutdown error:', getErrorMessage(err)); + }), + ); + } + if (app.locals.mcpClient) { tasks.push( app.locals.mcpClient.shutdown().catch((err) => { @@ -641,6 +694,22 @@ async function stopServices(app) { ); } + if (app.locals.browserExtensionGateway?.close) { + tasks.push( + app.locals.browserExtensionGateway.close().catch((err) => { + console.error('[BrowserExtensionGateway] Shutdown error:', getErrorMessage(err)); + }), + ); + } + + if (app.locals.desktopCompanionGateway?.close) { + tasks.push( + app.locals.desktopCompanionGateway.close().catch((err) => { + console.error('[DesktopCompanionGateway] Shutdown error:', getErrorMessage(err)); + }), + ); + } + if (app.locals.browserControllers instanceof Map) { for (const controller of app.locals.browserControllers.values()) { tasks.push( @@ -659,6 +728,23 @@ async function stopServices(app) { ); } + if ( + app.locals.voiceRuntimeManager + && typeof app.locals.voiceRuntimeManager.shutdown === 'function' + ) { + tasks.push( + app.locals.voiceRuntimeManager.shutdown().then((status) => { + if (status.timedOut) { + console.warn('[VoiceRuntime] Shutdown timed out while sessions were closing'); + } else { + logServiceReady('Voice runtime shutdown complete'); + } + }).catch((err) => { + console.error('[VoiceRuntime] Shutdown error:', getErrorMessage(err)); + }), + ); + } + if (app.locals.runtimeManager) { tasks.push( app.locals.runtimeManager.shutdown().catch((err) => { diff --git a/server/services/memory/embedding_index.js b/server/services/memory/embedding_index.js index ed28a1a8..e668172f 100644 --- a/server/services/memory/embedding_index.js +++ b/server/services/memory/embedding_index.js @@ -95,6 +95,8 @@ function findEmbeddingCandidates(db, { userId, agentId, embedding, + embeddingProvider = null, + embeddingModel = null, limit = DEFAULT_CANDIDATE_LIMIT, }) { const vector = typeof embedding === 'string' @@ -104,17 +106,25 @@ function findEmbeddingCandidates(db, { if (!probesByBand.length) return []; const matches = new Map(); + const embeddingSpace = embeddingProvider && embeddingModel + ? `${embeddingProvider}:${embeddingModel}` + : ''; for (const band of probesByBand) { const placeholders = band.values.map(() => '?').join(', '); const rows = db.prepare( - `SELECT memory_id - FROM memory_embedding_bands - WHERE user_id = ? - AND agent_id = ? - AND dimension = ? - AND index_version = ? - AND band_index = ? - AND band_value IN (${placeholders})` + `SELECT bands.memory_id + FROM memory_embedding_bands bands + JOIN memories memory ON memory.id = bands.memory_id + WHERE bands.user_id = ? + AND bands.agent_id = ? + AND bands.dimension = ? + AND bands.index_version = ? + AND bands.band_index = ? + AND bands.band_value IN (${placeholders}) + AND ( + ? = '' + OR (memory.embedding_provider || ':' || memory.embedding_model) = ? + )` ).all( userId, agentId || '', @@ -122,6 +132,8 @@ function findEmbeddingCandidates(db, { INDEX_VERSION, band.bandIndex, ...band.values, + embeddingSpace, + embeddingSpace, ); for (const row of rows) { matches.set(row.memory_id, (matches.get(row.memory_id) || 0) + 1); diff --git a/server/services/memory/embeddings.js b/server/services/memory/embeddings.js index f840d548..8694ed25 100644 --- a/server/services/memory/embeddings.js +++ b/server/services/memory/embeddings.js @@ -4,134 +4,192 @@ * Embedding helpers for the semantic memory system. * * Provider selection (in priority order): - * 1. Google (text-embedding-004, 768 dims) — when provider hint is 'google' and GOOGLE_AI_KEY is set + * 1. Google (gemini-embedding-2, 768 dims) — when provider hint is 'google' and GOOGLE_AI_KEY is set * 2. OpenAI (text-embedding-3-small, 1536 dims) — when OPENAI_API_KEY is set * 3. Keyword fallback — when no API key is available */ const https = require('https'); +const { + createAbortError, + isAbortError, + throwIfAborted, +} = require('../../utils/abort'); const OPENAI_MODEL = 'text-embedding-3-small'; const OPENAI_DIM = 1536; -const GOOGLE_MODEL = 'text-embedding-004'; +const GOOGLE_MODEL = 'gemini-embedding-2'; const GOOGLE_DIM = 768; +const EMBEDDING_TIMEOUT_MS = 15000; +const MAX_EMBEDDING_RESPONSE_BYTES = 2 * 1024 * 1024; // Exported so callers can sanity-check stored vector dimensions if needed const EMBED_DIM = OPENAI_DIM; const EMBED_DIM_GOOGLE = GOOGLE_DIM; -async function getGeminiEmbedding(text) { - const apiKey = process.env.GOOGLE_AI_KEY; - if (!apiKey) return null; - if (!text || !text.trim()) return null; - - const truncated = text.slice(0, 25000); +function toEmbeddingVector(value, expectedDimensions) { + if (!Array.isArray(value) || value.length !== expectedDimensions) return null; + const vector = new Float32Array(value.length); + for (let index = 0; index < value.length; index += 1) { + const number = Number(value[index]); + if (!Number.isFinite(number)) return null; + vector[index] = number; + } + return vector; +} - return new Promise((resolve) => { +function requestEmbeddingJson({ hostname, path, headers, body, signal }) { + throwIfAborted(signal, 'Embedding request aborted.'); + return new Promise((resolve, reject) => { let settled = false; - const done = (value) => { + let request = null; + let response = null; + let timer = null; + + const cleanup = () => { + if (timer) clearTimeout(timer); + signal?.removeEventListener('abort', onAbort); + }; + const finish = (error, value = null) => { if (settled) return; settled = true; - resolve(value); + cleanup(); + if (error) reject(error); + else resolve(value); + }; + const onAbort = () => { + const error = createAbortError(signal, 'Embedding request aborted.'); + response?.destroy(error); + request?.destroy(error); + finish(error); }; + signal?.addEventListener('abort', onAbort, { once: true }); - const body = JSON.stringify({ - model: `models/${GOOGLE_MODEL}`, - content: { parts: [{ text: truncated }] } - }); + timer = setTimeout(() => { + request?.destroy(); + response?.destroy(); + finish(null, null); + }, EMBEDDING_TIMEOUT_MS); - const path = `/v1beta/models/${GOOGLE_MODEL}:embedContent?key=${apiKey}`; - const options = { - hostname: 'generativelanguage.googleapis.com', + request = https.request({ + hostname, path, method: 'POST', headers: { + ...headers, 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(body) + 'Content-Length': Buffer.byteLength(body), + }, + }, (incoming) => { + response = incoming; + if (settled) { + incoming.destroy(); + return; + } + if (incoming.statusCode < 200 || incoming.statusCode >= 300) { + incoming.destroy(); + finish(null, null); + return; + } + const contentLength = Number(incoming.headers?.['content-length']); + if ( + Number.isFinite(contentLength) + && contentLength > MAX_EMBEDDING_RESPONSE_BYTES + ) { + incoming.destroy(); + finish(null, null); + return; } - }; - const req = https.request(options, (res) => { - let data = ''; - res.on('data', chunk => { data += chunk; }); - res.on('end', () => { + const chunks = []; + let totalBytes = 0; + incoming.on('data', (chunk) => { + if (settled) return; + const buffer = Buffer.from(chunk); + totalBytes += buffer.byteLength; + if (totalBytes > MAX_EMBEDDING_RESPONSE_BYTES) { + incoming.destroy(); + finish(null, null); + return; + } + chunks.push(buffer); + }); + incoming.on('end', () => { + if (settled) return; try { - const parsed = JSON.parse(data); - const vec = parsed.embedding?.values; - if (!vec) return done(null); - done(new Float32Array(vec)); + finish(null, JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8'))); } catch { - done(null); + finish(null, null); } }); + incoming.on('error', (error) => { + if (isAbortError(error, signal)) finish(createAbortError(signal)); + else finish(null, null); + }); + incoming.on('aborted', () => { + if (signal?.aborted) finish(createAbortError(signal)); + else finish(null, null); + }); }); - req.on('error', () => done(null)); - req.setTimeout(15000, () => { - req.destroy(); - done(null); + request.on('error', (error) => { + if (isAbortError(error, signal)) finish(createAbortError(signal)); + else finish(null, null); }); - req.write(body); - req.end(); + request.end(body); }); } -async function getOpenAIEmbedding(text) { - const apiKey = process.env.OPENAI_API_KEY; +function formatGoogleEmbeddingInput(text, inputType) { + if (inputType === 'query') return `task: search result | query: ${text}`; + if (inputType === 'document') return `title: none | text: ${text}`; + return `task: sentence similarity | query: ${text}`; +} + +async function getGeminiEmbedding(text, options = {}) { + const apiKey = process.env.GOOGLE_AI_KEY; if (!apiKey) return null; if (!text || !text.trim()) return null; - const truncated = text.slice(0, 25000); - - return new Promise((resolve) => { - let settled = false; - const done = (value) => { - if (settled) return; - settled = true; - resolve(value); - }; - - const body = JSON.stringify({ - model: OPENAI_MODEL, - input: truncated, - encoding_format: 'float' - }); + const truncated = formatGoogleEmbeddingInput( + text.slice(0, 25000), + options.inputType, + ); + const body = JSON.stringify({ + model: `models/${GOOGLE_MODEL}`, + content: { parts: [{ text: truncated }] }, + output_dimensionality: GOOGLE_DIM, + }); + const data = await requestEmbeddingJson({ + hostname: 'generativelanguage.googleapis.com', + path: `/v1beta/models/${GOOGLE_MODEL}:embedContent`, + headers: { 'x-goog-api-key': apiKey }, + body, + signal: options.signal, + }); + return toEmbeddingVector(data?.embedding?.values, GOOGLE_DIM); +} - const options = { - hostname: 'api.openai.com', - path: '/v1/embeddings', - method: 'POST', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(body) - } - }; +async function getOpenAIEmbedding(text, options = {}) { + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) return null; + if (!text || !text.trim()) return null; - const req = https.request(options, (res) => { - let data = ''; - res.on('data', chunk => { data += chunk; }); - res.on('end', () => { - try { - const parsed = JSON.parse(data); - if (parsed.error) return done(null); - const vec = parsed.data?.[0]?.embedding; - if (!vec) return done(null); - done(new Float32Array(vec)); - } catch { - done(null); - } - }); - }); + const truncated = text.slice(0, 25000); - req.on('error', () => done(null)); - req.setTimeout(15000, () => { - req.destroy(); - done(null); - }); - req.write(body); - req.end(); + const body = JSON.stringify({ + model: OPENAI_MODEL, + input: truncated, + encoding_format: 'float', + }); + const data = await requestEmbeddingJson({ + hostname: 'api.openai.com', + path: '/v1/embeddings', + headers: { Authorization: `Bearer ${apiKey}` }, + body, + signal: options.signal, }); + return toEmbeddingVector(data?.data?.[0]?.embedding, OPENAI_DIM); } /** @@ -140,15 +198,16 @@ async function getOpenAIEmbedding(text) { * @param {string} [provider] - 'google' to prefer Gemini embeddings * @returns {Float32Array|null} */ -async function getEmbedding(text, provider) { - const result = await getEmbeddingWithMetadata(text, provider); +async function getEmbedding(text, provider, options = {}) { + const result = await getEmbeddingWithMetadata(text, provider, options); return result?.vector || null; } -async function getEmbeddingWithMetadata(text, provider) { +async function getEmbeddingWithMetadata(text, provider, options = {}) { if (!text || !text.trim()) return null; + throwIfAborted(options.signal, 'Embedding request aborted.'); if (provider === 'google' && process.env.GOOGLE_AI_KEY) { - const vec = await getGeminiEmbedding(text); + const vec = await getGeminiEmbedding(text, options); if (vec) { return { vector: vec, @@ -158,7 +217,7 @@ async function getEmbeddingWithMetadata(text, provider) { }; } } - const vec = await getOpenAIEmbedding(text); + const vec = await getOpenAIEmbedding(text, options); if (!vec) return null; return { vector: vec, @@ -228,5 +287,7 @@ module.exports = { deserializeEmbedding, keywordSimilarity, EMBED_DIM, - EMBED_DIM_GOOGLE + EMBED_DIM_GOOGLE, + GOOGLE_MODEL, + OPENAI_MODEL, }; diff --git a/server/services/memory/ingestion.js b/server/services/memory/ingestion.js index bafac990..9b8e07c4 100644 --- a/server/services/memory/ingestion.js +++ b/server/services/memory/ingestion.js @@ -4,6 +4,11 @@ const { v4: uuidv4 } = require('uuid'); const db = require('../../db/database'); const { resolveAgentId } = require('../agents/manager'); const { getErrorMessage } = require('../bootstrap_helpers'); +const { + createLinkedAbortController, + isAbortError, + throwIfAborted, +} = require('../../utils/abort'); const { ingestDocuments } = require('./ingestion_documents'); const { decorateProviderSnapshot, @@ -39,6 +44,7 @@ class MemoryIngestionService { this.setInterval = setIntervalFn; this.clearInterval = clearIntervalFn; this.timer = null; + this.abortController = new AbortController(); this.activeBatches = new Map(); this.activeConnections = new Map(); this.stopping = false; @@ -67,6 +73,9 @@ class MemoryIngestionService { throw new Error('Memory ingestion cannot start while shutdown is in progress.'); } + if (this.abortController.signal.aborted) { + this.abortController = new AbortController(); + } this.stopping = false; this.state = 'running'; this.lastError = null; @@ -82,6 +91,7 @@ class MemoryIngestionService { try { await this.refreshDueConnections(); } catch (err) { + if (isAbortError(err, this.abortController.signal) && this.stopping) return; this.lastError = getErrorMessage(err); console.warn('[MemoryIngestion] Background refresh failed:', this.lastError); } @@ -91,6 +101,7 @@ class MemoryIngestionService { if (this.stopPromise) return this.stopPromise; this.stopping = true; this.state = 'stopping'; + this.abortController.abort('Memory ingestion service is stopping.'); if (this.timer) this.clearInterval(this.timer); this.timer = null; @@ -111,13 +122,27 @@ class MemoryIngestionService { } async ingestDocuments(userId, documents = [], options = {}) { - return ingestDocuments(this, userId, documents, options); + const linked = createLinkedAbortController([ + this.abortController.signal, + options.signal, + ]); + try { + return await ingestDocuments(this, userId, documents, { + ...options, + signal: linked.signal, + }); + } finally { + linked.cleanup(); + } } refreshDueConnections(userId = null) { const scopeKey = userId == null ? 'all' : `user:${userId}`; - if (this.stopping) { - return Promise.resolve({ skipped: true, reason: 'service_stopping' }); + if (this.stopping || this.abortController.signal.aborted) { + return Promise.resolve({ + skipped: true, + reason: this.stopping ? 'service_stopping' : 'service_stopped', + }); } const active = this.activeBatches.get(scopeKey); if (active) return active; @@ -132,6 +157,7 @@ class MemoryIngestionService { } async _refreshDueConnections(userId = null) { + const signal = this.abortController.signal; this.lastRunAt = new Date().toISOString(); try { const params = []; @@ -145,8 +171,8 @@ class MemoryIngestionService { const connections = this.db.prepare(sql).all(...params); const results = []; for (const connection of connections) { - if (this.stopping) break; - results.push(await this._refreshConnectionSafely(connection)); + if (this.stopping || signal.aborted) break; + results.push(await this._refreshConnectionSafely(connection, { signal })); } if (!results.some((result) => result?.status === 'failed')) { this.lastError = null; @@ -154,19 +180,28 @@ class MemoryIngestionService { this.lastCompletedAt = new Date().toISOString(); return { refreshed: results.length, results }; } catch (err) { + if (isAbortError(err, signal) && this.stopping) { + return { refreshed: 0, results: [], cancelled: true }; + } this.lastError = getErrorMessage(err); throw err; } } - _refreshConnectionSafely(connection) { + _refreshConnectionSafely(connection, options = {}) { const connectionKey = `${connection.user_id}:${connection.id}`; const active = this.activeConnections.get(connectionKey); if (active) return active; const promise = Promise.resolve() - .then(() => this.refreshConnection(connection)) + .then(() => this.refreshConnection(connection, options)) .catch((err) => { + if (isAbortError(err, options.signal) && this.stopping) { + return { + connectionId: connection.id, + status: 'cancelled', + }; + } const error = getErrorMessage(err); this.lastError = error; console.warn( @@ -214,7 +249,9 @@ class MemoryIngestionService { } } - async refreshConnection(connection) { + async refreshConnection(connection, options = {}) { + const signal = options.signal || this.abortController.signal; + throwIfAborted(signal, 'Memory ingestion refresh aborted.'); const sourceTypes = sourceTypesForConnection(connection.provider_key, connection.app_key); if (sourceTypes.length === 0) { return { connectionId: connection.id, status: 'not_supported' }; @@ -242,9 +279,12 @@ class MemoryIngestionService { connection, sourceTypes, cursor: latestJob?.cursor || {}, + signal, }); } catch (err) { - this._recordCollectorFailure(connection, primarySource, policy, agentId, err); + if (!isAbortError(err, signal)) { + this._recordCollectorFailure(connection, primarySource, policy, agentId, err); + } throw err; } return this.ingestDocuments(connection.user_id, collected.documents || [], { @@ -258,6 +298,7 @@ class MemoryIngestionService { sourceTypes, cursor: collected.cursor || null, }, + signal, }); } diff --git a/server/services/memory/ingestion_documents.js b/server/services/memory/ingestion_documents.js index 255459b5..66690969 100644 --- a/server/services/memory/ingestion_documents.js +++ b/server/services/memory/ingestion_documents.js @@ -12,8 +12,11 @@ const { safeTrim, } = require('./ingestion_support'); const { chunkDocument, overlapWindowChunks } = require('./ingestion_chunking'); +const { isAbortError, throwIfAborted } = require('../../utils/abort'); async function ingestDocuments(service, userId, documents = [], options = {}) { + const signal = options.signal || null; + throwIfAborted(signal, 'Memory ingestion aborted.'); const agentId = resolveAgentId(userId, options.agentId || options.agent_id || null); const normalizedDocs = (Array.isArray(documents) ? documents : []) .map((document) => normalizeDocument(document, { @@ -45,6 +48,7 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { const memoryIds = []; try { for (const document of normalizedDocs) { + throwIfAborted(signal, 'Memory ingestion aborted.'); const documentId = service.memoryManager.upsertIngestionDocument( userId, document, @@ -55,6 +59,7 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { const chunks = chunkDocument(document); const savedChunkMemoryIds = []; for (const chunk of chunks) { + throwIfAborted(signal, 'Memory ingestion aborted.'); const memoryId = await service.memoryManager.saveMemory( userId, `${document.title}\n${chunk.content}`, @@ -87,6 +92,7 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { charEnd: chunk.charEnd, }, }, + signal, }, ); if (!memoryId) continue; @@ -110,6 +116,7 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { const overlapChunks = overlapWindowChunks(chunks); for (const window of overlapChunks) { + throwIfAborted(signal, 'Memory ingestion aborted.'); const windowMemoryId = await service.memoryManager.saveMemory( userId, `${document.title}\n${window.content}`, @@ -143,6 +150,7 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { }, isOverlapWindow: true, }, + signal, }, ); if (windowMemoryId) savedChunkMemoryIds.push(windowMemoryId); @@ -151,6 +159,7 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { service.memoryManager.pruneSourceChunks(documentId, retainedChunkIds); } + throwIfAborted(signal, 'Memory ingestion aborted.'); service.memoryManager.recordIngestionJob(userId, { id: jobId, sourceType, @@ -174,17 +183,18 @@ async function ingestDocuments(service, userId, documents = [], options = {}) { knowledgeViews, }; } catch (err) { + const cancelled = isAbortError(err, signal); service.memoryManager.recordIngestionJob(userId, { id: jobId, sourceType, providerKey: safeTrim(options.providerKey, 80), connectionId, - status: 'failed', + status: cancelled ? 'cancelled' : 'failed', freshnessPolicy: policy, documentCount: documentIds.length, - error: err.message, + error: cancelled ? null : err.message, completedAt: new Date().toISOString(), - nextSyncAt: nextSyncFromPolicy(policy), + nextSyncAt: cancelled ? null : nextSyncFromPolicy(policy), }, { agentId }); throw err; } diff --git a/server/services/memory/manager.js b/server/services/memory/manager.js index c0d6d002..6f9f4c85 100644 --- a/server/services/memory/manager.js +++ b/server/services/memory/manager.js @@ -3,7 +3,6 @@ const path = require('path'); const { v4: uuidv4 } = require('uuid'); const db = require('../../db/database'); const { - getEmbedding, getEmbeddingWithMetadata, cosineSimilarity, serializeEmbedding, @@ -41,11 +40,14 @@ const { isLocalEncryptedValue, } = require('../../utils/local_secrets'); -async function getActiveProvider(userId, agentId = null) { +async function getActiveProvider(userId, agentId = null, options = {}) { try { const { getSupportedModels } = require('../ai/models'); + const { resolveModelSelection } = require('../ai/model_identity'); const { getAiSettings } = require('../ai/settings'); - const models = await getSupportedModels(userId, agentId); + const models = await getSupportedModels(userId, agentId, { + signal: options.signal, + }); const aiSettings = getAiSettings(userId, agentId); const defaultChatModel = aiSettings.default_chat_model || null; const enabledIds = Array.isArray(aiSettings.enabled_models) ? aiSettings.enabled_models : null; @@ -55,7 +57,10 @@ async function getActiveProvider(userId, agentId = null) { : (Array.isArray(enabledIds) && enabledIds.length > 0 ? enabledIds[0] : null); if (modelId) { - const def = models.find(m => m.id === modelId && m.available !== false); + const def = resolveModelSelection( + models.filter((model) => model.available !== false), + modelId, + ); if (def) return def.provider; } } catch { } @@ -1649,29 +1654,32 @@ class MemoryManager { const memoryHash = stableHash(`${category}:${content}`); const summary = summarizeForPrompt({ content, entities: extractEntities(content) }); - const embeddingResult = await getEmbeddingWithMetadata( - content, - await getActiveProvider(userId, agentId), - ); - const embedding = embeddingResult?.vector || null; - const exact = this._findExactMemory(userId, agentId, memoryHash, scope); if (exact?.id) { return this._reinforceExactMemory(exact.id, importance); } + const embeddingResult = await getEmbeddingWithMetadata( + content, + await getActiveProvider(userId, agentId, options), + { signal: options.signal, inputType: 'document' }, + ); + const embedding = embeddingResult?.vector || null; + const duplicateCandidateIds = embedding ? findEmbeddingCandidates(db, { userId, agentId, embedding, + embeddingProvider: embeddingResult.provider, + embeddingModel: embeddingResult.model, limit: 120, }).map((candidate) => candidate.memory_id) : this._searchMemoryFts(userId, agentId, content, 120) .map((candidate) => candidate.memory_id); const existing = duplicateCandidateIds.length ? db.prepare( - `SELECT id, content, embedding, metadata_json + `SELECT id, content, embedding, embedding_provider, embedding_model, metadata_json FROM memories WHERE id IN (${duplicateCandidateIds.map(() => '?').join(', ')}) AND user_id = ? AND agent_id = ? AND archived = 0 @@ -1688,7 +1696,12 @@ class MemoryManager { for (const mem of structuredFacts.length ? [] : existing) { let sim = 0; - if (embedding && mem.embedding) { + if ( + embedding + && mem.embedding + && mem.embedding_provider === embeddingResult?.provider + && mem.embedding_model === embeddingResult?.model + ) { const memVec = deserializeEmbedding(mem.embedding); if (memVec) sim = cosineSimilarity(embedding, memVec); } else { @@ -1839,6 +1852,7 @@ class MemoryManager { conversationId: options.conversationId || null, runId: options.runId || null, }, + signal: options.signal, }, ); if (memoryId) memoryIds.push(memoryId); @@ -1860,9 +1874,18 @@ class MemoryManager { const route = routeMemoryQuery(query); const suppliedQueryEmbedding = options.queryEmbedding; - const queryVec = suppliedQueryEmbedding - ? new Float32Array(Array.from(suppliedQueryEmbedding, Number)) - : await getEmbedding(query, await getActiveProvider(userId, agentId)); + const queryEmbeddingResult = suppliedQueryEmbedding + ? { + vector: new Float32Array(Array.from(suppliedQueryEmbedding, Number)), + provider: options.queryEmbeddingProvider || null, + model: options.queryEmbeddingModel || null, + } + : await getEmbeddingWithMetadata( + query, + await getActiveProvider(userId, agentId, options), + { signal: options.signal, inputType: 'query' }, + ); + const queryVec = queryEmbeddingResult?.vector || null; const lexicalHits = this._searchMemoryFts(userId, agentId, query, Math.max(80, limit * 12)); const entityHits = this._searchEntityMemoryIds(userId, agentId, query, Math.max(80, limit * 12)); const vectorHits = queryVec @@ -1870,6 +1893,8 @@ class MemoryManager { userId, agentId, embedding: queryVec, + embeddingProvider: queryEmbeddingResult?.provider, + embeddingModel: queryEmbeddingResult?.model, limit: Math.min(500, Math.max(300, limit * 20)), }) : []; @@ -1917,7 +1942,8 @@ class MemoryManager { let all = db.prepare( `SELECT id, category, content, summary, importance, confidence, embedding, access_count, memory_strength, last_accessed_at, pinned, created_at, updated_at, - scope_type, scope_id, source_type, source_id, source_label, stale_after_days, metadata_json + scope_type, scope_id, source_type, source_id, source_label, stale_after_days, + embedding_provider, embedding_model, metadata_json FROM memories WHERE id IN (${candidatePlaceholders}) AND user_id = ? AND agent_id = ? AND archived = 0 @@ -1997,7 +2023,13 @@ class MemoryManager { const semanticScored = all.map(mem => { let semanticScore = 0; - if (queryVec && mem.embedding) { + const sameEmbeddingSpace = !queryEmbeddingResult?.provider + || !queryEmbeddingResult?.model + || ( + mem.embedding_provider === queryEmbeddingResult.provider + && mem.embedding_model === queryEmbeddingResult.model + ); + if (queryVec && mem.embedding && sameEmbeddingSpace) { const memVec = deserializeEmbedding(mem.embedding); if (memVec) { semanticScore = cosineSimilarity(queryVec, memVec); @@ -2126,7 +2158,7 @@ class MemoryManager { /** * Update a memory's content and/or importance. */ - async updateMemory(id, { content, importance, category }) { + async updateMemory(id, { content, importance, category, signal = null }) { const mem = db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id); if (!mem) return null; @@ -2141,7 +2173,8 @@ class MemoryManager { if (content && content !== mem.content) { embeddingResult = await getEmbeddingWithMetadata( newContent, - await getActiveProvider(mem.user_id, mem.agent_id), + await getActiveProvider(mem.user_id, mem.agent_id, { signal }), + { signal, inputType: 'document' }, ); newEmbed = embeddingResult?.vector ? serializeEmbedding(embeddingResult.vector) diff --git a/server/services/messaging/automation.js b/server/services/messaging/automation.js index b8ef8603..23748a94 100644 --- a/server/services/messaging/automation.js +++ b/server/services/messaging/automation.js @@ -20,17 +20,53 @@ const { } = require('../voice/runtime'); const { getErrorMessage } = require('../bootstrap_helpers'); const { processInboundQueue } = require('./inbound_queue'); +const { attachRunToInboundJobs } = require('./inbound_store'); const { startTypingKeepalive } = require('./typing_keepalive'); +const { waitForBoundedResult } = require('../network/http'); +const { createAbortError, throwIfAborted } = require('../../utils/abort'); function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) { const userQueues = Object.create(null); + const activeHandlers = new Set(); + const abortController = new AbortController(); + const runtime = { + shuttingDown: false, + shutdownPromise: null, + shutdown() { + if (this.shutdownPromise) return this.shutdownPromise; + this.shuttingDown = true; + const error = new Error('Messaging automation is shutting down.'); + error.name = 'AbortError'; + error.code = 'MESSAGING_AUTOMATION_SHUTDOWN'; + abortController.abort(error); + for (const queue of Object.values(userQueues)) { + queue.cancelRequested = true; + queue.cancelPending?.(); + } + this.shutdownPromise = waitForBoundedResult( + Promise.allSettled(Array.from(activeHandlers)), + { + serviceName: 'Messaging automation', + timeoutMs: 10000, + }, + ).then(() => ({ state: 'stopped', timedOut: false })).catch((error) => ({ + state: 'timeout', + timedOut: error?.code === 'HTTP_TIMEOUT', + error: error?.message || String(error), + })); + return this.shutdownPromise; + }, + }; app.locals.userQueues = userQueues; + app.locals.messagingAutomationRuntime = runtime; - messagingManager.registerHandler(async (userId, msg) => { + const handleMessage = async (userId, msg, signal) => { + throwIfAborted(signal, 'Messaging automation stopped before handling the message.'); const agentId = msg.agentId || null; if (!(await isAllowedMessagingSender({ io, userId, msg }))) { return; } + throwIfAborted(signal, 'Messaging automation stopped before handling the message.'); const commandRouter = app?.locals?.commandRouter; if (commandRouter) { @@ -42,9 +78,11 @@ function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) source: 'messaging', platform: msg.platform, chatId: msg.chatId, - sender: msg.sender + sender: msg.sender, + signal, }); } catch (err) { + if (signal?.aborted) throw createAbortError(signal); console.error(`[Messaging] Command dispatch failed on ${msg.platform}:`, err.message); io.to(`user:${userId}`).emit('messaging:error', { error: `Command dispatch failed on ${msg.platform}: ${err.message}` @@ -55,9 +93,10 @@ function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) msg.platform, msg.chatId, `Command handling failed: ${err.message}`, - { runId: null, agentId } + { runId: null, agentId, signal } ); } catch (sendErr) { + if (signal?.aborted) throw createAbortError(signal); console.error(`[Messaging] Failed to report command dispatch error on ${msg.platform}:`, sendErr.message); io.to(`user:${userId}`).emit('messaging:error', { error: `Command handling failed and the error report could not be sent on ${msg.platform}: ${sendErr.message}` @@ -78,9 +117,10 @@ function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) msg.platform, msg.chatId, commandResult.content || 'Done.', - { runId: null, agentId } + { runId: null, agentId, signal } ); } catch (err) { + if (signal?.aborted) throw createAbortError(signal); console.error(`[Messaging] Failed to send command response on ${msg.platform}:`, err.message); io.to(`user:${userId}`).emit('messaging:error', { error: `Command executed but response could not be sent on ${msg.platform}: ${err.message}` @@ -98,12 +138,13 @@ function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) upsertSetting.run(userId, agentId, 'last_platform', msg.platform); upsertSetting.run(userId, agentId, 'last_chat_id', msg.chatId); - await processQueuedMessage({ + return processQueuedMessage({ userQueues, messagingManager, agentEngine, userId, msg, + signal, onProcessingError: ({ error, runId, failedMessage }) => { const errorMessage = getErrorMessage(error); console.error( @@ -118,7 +159,23 @@ function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) }); } }); + }; + messagingManager.registerHandler((userId, msg) => { + if (runtime.shuttingDown) return null; + const promise = handleMessage(userId, msg, abortController.signal); + activeHandlers.add(promise); + const cleanup = () => activeHandlers.delete(promise); + promise.then(cleanup, cleanup); + return promise; }); + if (typeof messagingManager.recoverPendingInbound === 'function') { + void messagingManager.recoverPendingInbound().catch((error) => { + if (!runtime.shuttingDown) { + console.error('[MessagingAutomation] Inbound recovery failed:', getErrorMessage(error)); + } + }); + } + return runtime; } async function processQueuedMessage({ @@ -127,6 +184,7 @@ async function processQueuedMessage({ agentEngine, userId, msg, + signal = null, onProcessingError = null }) { return processInboundQueue({ @@ -138,7 +196,8 @@ async function processQueuedMessage({ messagingManager, agentEngine, userId, - msg: queuedMessage + msg: queuedMessage, + signal, }), onProcessingError }); @@ -148,10 +207,17 @@ async function executeQueuedMessage({ messagingManager, agentEngine, userId, - msg + msg, + signal = null, }) { + throwIfAborted(signal, 'Messaging request aborted before execution.'); const agentId = msg.agentId || null; const runId = randomUUID(); + const inboundJobIds = Array.from(new Set([ + ...(Array.isArray(msg.inboundJobIds) ? msg.inboundJobIds : []), + msg.inboundJobId, + ].map((value) => String(value || '').trim()).filter(Boolean))); + if (inboundJobIds.length) attachRunToInboundJobs(inboundJobIds, runId); const reportSideEffectError = (operation, error) => { console.warn( `[MessagingAutomation] ${operation} failed platform=${msg.platform} user=${userId}:`, @@ -165,7 +231,7 @@ async function executeQueuedMessage({ msg.platform, msg.chatId, msg.messageId, - { agentId } + { agentId, signal } ); } catch (error) { reportSideEffectError('mark read', error); @@ -178,6 +244,7 @@ async function executeQueuedMessage({ runId, platform: msg.platform, chatId: msg.chatId, + signal, onError: reportSideEffectError }); @@ -199,6 +266,7 @@ async function executeQueuedMessage({ conversationId, source: msg.platform, chatId: msg.chatId, + messagingInboundJobId: inboundJobIds[0] || null, context: { rawUserMessage: msg.content } }; @@ -208,10 +276,17 @@ async function executeQueuedMessage({ ]; } + runOptions.messagingInboundJobId = inboundJobIds[0] || null; + runOptions.signal = signal; + const result = await agentEngine.run(userId, prompt, runOptions); return { runId, result, error: null }; } catch (error) { - return { runId, result: null, error }; + return { + runId, + result: null, + error: signal?.aborted ? createAbortError(signal) : error, + }; } finally { await stopTypingKeepalive(); } diff --git a/server/services/messaging/http_platforms.js b/server/services/messaging/http_platforms.js index e772be34..e6e3df12 100644 --- a/server/services/messaging/http_platforms.js +++ b/server/services/messaging/http_platforms.js @@ -4,6 +4,7 @@ const crypto = require('crypto'); const net = require('net'); const tls = require('tls'); const { BasePlatform } = require('./base'); +const { fetchResponseText } = require('../network/http'); function requireText(value, label) { const text = String(value || '').trim(); @@ -51,14 +52,18 @@ function parseHeaders(value) { } async function fetchJson(url, options = {}, serviceName = 'Messaging platform') { - const response = await fetch(url, { + const { response, text } = await fetchResponseText(url, { ...options, headers: { ...(options.body == null ? {} : { 'content-type': 'application/json' }), ...(options.headers || {}), }, + maxResponseBytes: Number(options.maxResponseBytes) > 0 + ? Number(options.maxResponseBytes) + : 2 * 1024 * 1024, + serviceName, + timeoutMs: Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : 15000, }); - const text = await response.text(); let body = null; if (text) { try { body = JSON.parse(text); } catch { body = text; } @@ -71,7 +76,13 @@ async function fetchJson(url, options = {}, serviceName = 'Messaging platform') .replace(/https?:\/\/[^\s"'<>]+/gi, '[redacted-url]') .replace(/\b(token|access_token|refresh_token|authorization)\b\s*[:=]\s*"[^"]*"/gi, '$1=[redacted]') .replace(/\b(token|access_token|refresh_token|authorization)\b\s*[:=]\s*[^,\s;"'}\]]+/gi, '$1=[redacted]'); - throw new Error(`${serviceName} request failed (${response.status}): ${detail || response.statusText}`); + const error = new Error( + `${serviceName} request failed (${response.status}): ${detail || response.statusText}`, + ); + error.status = response.status; + error.headers = response.headers; + error.safeToRetry = response.status === 429; + throw error; } return body; } @@ -173,7 +184,7 @@ class ConfigurableHttpPlatform extends BasePlatform { }; } - async sendMessage(to, content) { + async sendMessage(to, content, options = {}) { const urlTemplate = this.config.outboundUrl || this.config.webhookUrl || this.defaults.outboundUrl || this.defaults.webhookUrl; if (!urlTemplate) throw new Error(`${this.defaults.label || this.name} outbound URL is not configured`); @@ -209,6 +220,7 @@ class ConfigurableHttpPlatform extends BasePlatform { method: this.config.method || this.defaults.method || 'POST', headers, body: JSON.stringify(body), + signal: options.signal, }, this.defaults.label || this.name); return { success: true }; } @@ -284,12 +296,13 @@ class SlackPlatform extends BasePlatform { return this._botUserId ? { username: this._botUserId } : null; } - async sendMessage(to, content) { + async sendMessage(to, content, options = {}) { if (!this.botToken) throw new Error('Slack bot token is required for outbound messages'); const result = await fetchJson('https://slack.com/api/chat.postMessage', { method: 'POST', headers: { Authorization: `Bearer ${this.botToken}` }, body: JSON.stringify({ channel: to, text: content }), + signal: options.signal, }, 'Slack'); if (result && result.ok === false) throw new Error(`Slack post failed: ${result.error || 'unknown error'}`); return { success: true, ts: result?.ts }; @@ -593,12 +606,13 @@ class MatrixPlatform extends BasePlatform { } } - async sendMessage(to, content) { + async sendMessage(to, content, options = {}) { if (this.status !== 'connected') throw new Error('Matrix not connected'); const txnId = encodeURIComponent(crypto.randomUUID()); await this.#matrix(`/rooms/${encodeURIComponent(to)}/send/m.room.message/${txnId}`, { method: 'PUT', body: JSON.stringify({ msgtype: 'm.text', body: content }), + signal: options.signal, }); return { success: true }; } @@ -618,9 +632,9 @@ class BlueBubblesPlatform extends ConfigurableHttpPlatform { return { status: 'connected' }; } - async sendMessage(to, content) { + async sendMessage(to, content, options = {}) { if (this.config.outboundUrl || this.config.webhookUrl) { - return super.sendMessage(to, content); + return super.sendMessage(to, content, options); } const url = new URL(`${this.serverUrl}${this.config.sendPath || '/api/v1/message/text'}`); url.searchParams.set('guid', to); @@ -629,6 +643,7 @@ class BlueBubblesPlatform extends ConfigurableHttpPlatform { await fetchJson(url.toString(), { method: 'POST', body: JSON.stringify({ message: content }), + signal: options.signal, }, this.defaults.label); return { success: true }; } @@ -713,9 +728,9 @@ class SignalPlatform extends ConfigurableHttpPlatform { } } - async sendMessage(to, content) { + async sendMessage(to, content, options = {}) { if (this.config.outboundUrl || this.config.webhookUrl) { - return super.sendMessage(to, content); + return super.sendMessage(to, content, options); } await fetchJson(`${this.restUrl}/v2/send`, { method: 'POST', @@ -724,6 +739,7 @@ class SignalPlatform extends ConfigurableHttpPlatform { number: this.account, recipients: [to], }), + signal: options.signal, }, 'Signal'); return { success: true }; } @@ -743,7 +759,7 @@ class LinePlatform extends ConfigurableHttpPlatform { return { status: 'connected' }; } - async sendMessage(to, content) { + async sendMessage(to, content, options = {}) { const token = requireText(this.config.channelAccessToken || this.config.token, 'LINE channel access token'); await fetchJson('https://api.line.me/v2/bot/message/push', { method: 'POST', @@ -752,6 +768,7 @@ class LinePlatform extends ConfigurableHttpPlatform { to, messages: [{ type: 'text', text: content }], }), + signal: options.signal, }, 'LINE'); return { success: true }; } @@ -815,14 +832,17 @@ class MattermostPlatform extends ConfigurableHttpPlatform { return { status: 'connected' }; } - async sendMessage(to, content) { - if (this.config.webhookUrl || this.config.outboundUrl) return super.sendMessage(to, content); + async sendMessage(to, content, options = {}) { + if (this.config.webhookUrl || this.config.outboundUrl) { + return super.sendMessage(to, content, options); + } const baseUrl = trimTrailingSlash(this.config.baseUrl); const token = requireText(this.config.token, 'Mattermost token'); await fetchJson(`${baseUrl}/api/v4/posts`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ channel_id: to, message: content }), + signal: options.signal, }, 'Mattermost'); return { success: true }; } diff --git a/server/services/messaging/inbound_queue.js b/server/services/messaging/inbound_queue.js index 10e10a86..ebe6c0aa 100644 --- a/server/services/messaging/inbound_queue.js +++ b/server/services/messaging/inbound_queue.js @@ -2,6 +2,56 @@ const { getErrorMessage } = require('../bootstrap_helpers'); +function completionResult(outcome, cancelled = false) { + return { + runId: outcome?.runId || null, + result: outcome?.result || null, + error: outcome?.error || null, + cancelled, + }; +} + +function settleWaiters(item, result) { + for (const resolve of item?.waiters || []) resolve(result); + if (item?.waiters) item.waiters = []; +} + +function cancelPending(queue) { + const result = completionResult(null, true); + for (const item of queue.pending.splice(0)) settleWaiters(item, result); +} + +function queuedResult(queue, msg) { + let resolveCompletion; + const completion = new Promise((resolve) => { + resolveCompletion = resolve; + }); + const last = queue.pending[queue.pending.length - 1]; + if ( + last + && last.message.platform === msg.platform + && last.message.chatId === msg.chatId + && String(last.message.sender || '') === String(msg.sender || '') + ) { + last.message.content += `\n${msg.content}`; + last.message.messageId = msg.messageId; + last.message.inboundJobIds = Array.from(new Set([ + ...(last.message.inboundJobIds || []), + ...(msg.inboundJobIds || []), + msg.inboundJobId, + ].filter(Boolean))); + last.waiters.push(resolveCompletion); + } else { + queue.pending.push({ + message: { ...msg }, + waiters: [resolveCompletion], + }); + } + const result = { queued: true }; + Object.defineProperty(result, 'completion', { value: completion }); + return result; +} + async function processInboundQueue({ userQueues, userId, @@ -12,79 +62,83 @@ async function processInboundQueue({ const agentId = msg.agentId || null; const queueKey = `${userId}:${agentId || 'main'}`; if (!userQueues[queueKey]) { - userQueues[queueKey] = { running: false, pending: [], cancelRequested: false }; + userQueues[queueKey] = { + running: false, + pending: [], + cancelRequested: false, + cancelPending() { + cancelPending(this); + }, + }; } const queue = userQueues[queueKey]; if (queue.cancelRequested && !queue.running) { - queue.pending = []; + cancelPending(queue); queue.cancelRequested = false; } if (queue.running) { - const last = queue.pending[queue.pending.length - 1]; - if ( - last - && last.platform === msg.platform - && last.chatId === msg.chatId - && String(last.sender || '') === String(msg.sender || '') - ) { - last.content += `\n${msg.content}`; - last.messageId = msg.messageId; - } else { - queue.pending.push({ ...msg }); - } - return { queued: true }; + return queuedResult(queue, msg); } queue.running = true; - let currentMessage = msg; + let currentItem = { message: msg, waiters: [] }; let processedCount = 0; let failedCount = 0; let cancelled = false; + let initialOutcome = null; try { - while (currentMessage) { + while (currentItem) { let outcome; try { - outcome = await executeMessage(currentMessage); + outcome = await executeMessage(currentItem.message); } catch (error) { outcome = { runId: null, result: null, error }; } processedCount += 1; + const itemCancelled = queue.cancelRequested; + const itemResult = completionResult(outcome, itemCancelled); + if (!initialOutcome) initialOutcome = itemResult; + settleWaiters(currentItem, itemResult); - if (outcome?.error) { + if (outcome?.error && !queue.cancelRequested) { failedCount += 1; await notifyProcessingError(onProcessingError, { error: outcome.error, runId: outcome.runId, userId, - failedMessage: currentMessage + failedMessage: currentItem.message }); } if (queue.cancelRequested) { - queue.pending = []; + cancelPending(queue); cancelled = true; break; } - currentMessage = queue.pending.shift() || null; + currentItem = queue.pending.shift() || null; } } finally { queue.running = false; - queue.pending = []; + cancelPending(queue); queue.cancelRequested = false; if (userQueues[queueKey] === queue) { delete userQueues[queueKey]; } } - return { + const result = { processedCount, failedCount, cancelled }; + Object.defineProperty(result, 'outcome', { + value: initialOutcome || completionResult(null, cancelled), + }); + return result; } async function notifyProcessingError(handler, details) { diff --git a/server/services/messaging/inbound_store.js b/server/services/messaging/inbound_store.js new file mode 100644 index 00000000..6fb5f44f --- /dev/null +++ b/server/services/messaging/inbound_store.js @@ -0,0 +1,224 @@ +'use strict'; + +const { randomUUID } = require('node:crypto'); +const db = require('../../db/database'); + +const MAX_INBOUND_PAYLOAD_BYTES = 1024 * 1024; +const JOB_STATUSES = new Set(['pending', 'processing', 'completed', 'failed']); + +function encodePayload(payload) { + const encoded = JSON.stringify(payload); + if (Buffer.byteLength(encoded, 'utf8') > MAX_INBOUND_PAYLOAD_BYTES) { + const error = new Error('Inbound messaging payload exceeds the 1 MiB durability limit.'); + error.code = 'MESSAGING_INBOUND_PAYLOAD_TOO_LARGE'; + throw error; + } + return encoded; +} + +function decodePayload(value) { + try { + const payload = JSON.parse(String(value || '')); + return payload && typeof payload === 'object' && !Array.isArray(payload) + ? payload + : null; + } catch { + return null; + } +} + +function getJobByMessageId(messageId) { + return db.prepare( + 'SELECT * FROM messaging_inbound_jobs WHERE message_id = ?', + ).get(messageId) || null; +} + +function enqueueInboundMessage({ + userId, + agentId, + platform, + platformMessageId, + chatId, + content, + metadata, + createdAt, + payload, +}) { + return db.transaction(() => { + if (platformMessageId) { + const existing = db.prepare( + `SELECT id + FROM messages + WHERE user_id = ? AND platform = ? AND platform_msg_id = ? AND role = 'user' + LIMIT 1`, + ).get(userId, platform, platformMessageId); + if (existing) { + return { + created: false, + job: getJobByMessageId(existing.id), + messageId: existing.id, + }; + } + } + + const jobId = randomUUID(); + const durablePayload = { + ...payload, + inboundJobId: jobId, + inboundJobIds: [jobId], + }; + const insert = db.prepare( + `INSERT INTO messages ( + user_id, agent_id, role, content, platform, platform_msg_id, + platform_chat_id, metadata, created_at + ) VALUES (?, ?, 'user', ?, ?, ?, ?, ?, ?)`, + ).run( + userId, + agentId, + content, + platform, + platformMessageId || null, + chatId, + metadata ? JSON.stringify(metadata) : null, + createdAt || new Date().toISOString(), + ); + const messageId = Number(insert.lastInsertRowid); + db.prepare( + `INSERT INTO messaging_inbound_jobs ( + id, message_id, user_id, agent_id, platform, platform_msg_id, + platform_chat_id, payload_json, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')`, + ).run( + jobId, + messageId, + userId, + agentId, + platform, + platformMessageId || null, + chatId, + encodePayload(durablePayload), + ); + return { + created: true, + job: getJobByMessageId(messageId), + messageId, + payload: durablePayload, + }; + })(); +} + +function claimInboundJob(jobId) { + const result = db.prepare( + `UPDATE messaging_inbound_jobs + SET status = 'processing', + attempts = attempts + 1, + last_error = NULL, + updated_at = datetime('now') + WHERE id = ? AND status = 'pending'`, + ).run(jobId); + return result.changes === 1; +} + +function settleInboundJob(jobId, status, error = null) { + if (!JOB_STATUSES.has(status) || status === 'processing') { + throw new Error(`Invalid inbound messaging job status: ${status}`); + } + db.prepare( + `UPDATE messaging_inbound_jobs + SET status = ?, + last_error = ?, + completed_at = CASE WHEN ? IN ('completed', 'failed') THEN datetime('now') ELSE NULL END, + updated_at = datetime('now') + WHERE id = ?`, + ).run( + status, + error ? String(error).slice(0, 4000) : null, + status, + jobId, + ); +} + +function attachRunToInboundJobs(jobIds, runId) { + const ids = Array.from(new Set( + (Array.isArray(jobIds) ? jobIds : [jobIds]) + .map((value) => String(value || '').trim()) + .filter(Boolean), + )); + if (!ids.length || !runId) return 0; + const update = db.prepare( + `UPDATE messaging_inbound_jobs + SET run_id = ?, updated_at = datetime('now') + WHERE id = ? AND status = 'processing'`, + ); + return db.transaction(() => ids.reduce( + (count, id) => count + update.run(runId, id).changes, + 0, + ))(); +} + +function reconcileInterruptedInboundJobs() { + return db.transaction(() => { + const completed = db.prepare( + `UPDATE messaging_inbound_jobs + SET status = 'completed', completed_at = datetime('now'), updated_at = datetime('now') + WHERE status = 'processing' + AND run_id IN (SELECT id FROM agent_runs WHERE status = 'completed')`, + ).run().changes; + const failed = db.prepare( + `UPDATE messaging_inbound_jobs + SET status = 'failed', + last_error = COALESCE(last_error, 'The server restarted after this agent run began; it will not be replayed automatically.'), + completed_at = datetime('now'), + updated_at = datetime('now') + WHERE status = 'processing' + AND run_id IN (SELECT id FROM agent_runs)`, + ).run().changes; + const pending = db.prepare( + `UPDATE messaging_inbound_jobs + SET status = 'pending', + last_error = NULL, + updated_at = datetime('now') + WHERE status = 'processing' + AND (run_id IS NULL OR run_id NOT IN (SELECT id FROM agent_runs))`, + ).run().changes; + return { completed, failed, pending }; + })(); +} + +function listPendingInboundJobs(filters = {}) { + const clauses = ["status = 'pending'"]; + const values = []; + for (const [column, value] of [ + ['user_id', filters.userId], + ['agent_id', filters.agentId], + ['platform', filters.platform], + ]) { + if (value === undefined) continue; + if (value === null) clauses.push(`${column} IS NULL`); + else { + clauses.push(`${column} = ?`); + values.push(value); + } + } + const limit = Math.max(1, Math.min(500, Number(filters.limit) || 100)); + return db.prepare( + `SELECT * FROM messaging_inbound_jobs + WHERE ${clauses.join(' AND ')} + ORDER BY created_at ASC, id ASC + LIMIT ?`, + ).all(...values, limit); +} + +function payloadForInboundJob(job) { + return decodePayload(job?.payload_json); +} + +module.exports = { + attachRunToInboundJobs, + claimInboundJob, + enqueueInboundMessage, + listPendingInboundJobs, + payloadForInboundJob, + reconcileInterruptedInboundJobs, + settleInboundJob, +}; diff --git a/server/services/messaging/manager.js b/server/services/messaging/manager.js index ea7c0e64..ccec77f9 100644 --- a/server/services/messaging/manager.js +++ b/server/services/messaging/manager.js @@ -1,3 +1,5 @@ +'use strict'; + const EventEmitter = require('events'); const db = require('../../db/database'); const fs = require('fs'); @@ -36,8 +38,30 @@ const { } = require('./access_policy'); const { decryptValue, encryptValue } = require('../integrations/secrets'); const { readMeshtasticEnabled } = require('./meshtastic_env'); +const { + createLinkedAbortController, + isAbortError, + throwIfAborted, +} = require('../../utils/abort'); +const { waitForAbortableResult, waitForBoundedResult } = require('../network/http'); +const { + claimInboundJob, + enqueueInboundMessage, + listPendingInboundJobs, + payloadForInboundJob, + reconcileInterruptedInboundJobs, + settleInboundJob, +} = require('./inbound_store'); const LEGACY_WHATSAPP_AUTH_DIR = path.join(DATA_DIR, 'whatsapp-auth'); +const MESSAGING_OPERATION_TIMEOUT_MS = 60000; + +function messagingShutdownError() { + const error = new Error('Messaging is shutting down and cannot accept new work.'); + error.name = 'AbortError'; + error.code = 'MESSAGING_SHUTTING_DOWN'; + return error; +} class IrcMessagingPlatform extends IrcPlatform { constructor(config = {}) { super('irc', config); } @@ -64,6 +88,12 @@ class MessagingManager extends EventEmitter { this.accessSuggestions = new Map(); this.messageHandlers = []; this.isShuttingDown = false; + this.shutdownPromise = null; + this.lifecycleAbortController = new AbortController(); + this.activeOperations = new Set(); + this.activeInboundJobs = new Set(); + this.activeInboundRecoveries = new Map(); + this.inboundJobsReconciled = false; this.platformTypes = { whatsapp: WhatsAppPlatform, telnyx: TelnyxVoicePlatform, @@ -94,9 +124,48 @@ class MessagingManager extends EventEmitter { } registerHandler(handler) { + if (this.isShuttingDown) return false; if (!this.messageHandlers.includes(handler)) { this.messageHandlers.push(handler); } + return true; + } + + _assertRunning() { + if (this.isShuttingDown || this.lifecycleAbortController.signal.aborted) { + throw messagingShutdownError(); + } + } + + async _runOperation(options, serviceName, operation, timeoutMs = MESSAGING_OPERATION_TIMEOUT_MS) { + this._assertRunning(); + const timeoutController = new AbortController(); + const linked = createLinkedAbortController([ + options?.signal, + this.lifecycleAbortController.signal, + timeoutController.signal, + ]); + throwIfAborted(linked.signal, `${serviceName} aborted.`); + const timer = setTimeout(() => { + const error = new Error(`${serviceName} timed out after ${timeoutMs}ms.`); + error.code = 'MESSAGING_TIMEOUT'; + timeoutController.abort(error); + }, timeoutMs); + + const operationPromise = waitForAbortableResult( + Promise.resolve().then(() => operation(linked.signal)), + linked.signal, + `${serviceName} aborted.`, + ); + this.activeOperations.add(operationPromise); + const cleanupOperation = () => this.activeOperations.delete(operationPromise); + operationPromise.then(cleanupOperation, cleanupOperation); + try { + return await operationPromise; + } finally { + clearTimeout(timer); + linked.cleanup(); + } } async ingestMessage(userId, platformName, msg, options = {}) { @@ -119,53 +188,169 @@ class MessagingManager extends EventEmitter { senderTag: msg.senderTag, isGroup: msg.isGroup, mediaType: msg.mediaType, + localMediaPath: msg.localMediaPath || null, ...(msg.metadata && typeof msg.metadata === 'object' ? msg.metadata : {}), }; - // Deduplicate against platform_msg_id — webhook retries (at-least-once delivery) - // would otherwise trigger a second agent run for the same user message. - if (msg.messageId) { - const already = db.prepare( - "SELECT id FROM messages WHERE user_id = ? AND platform = ? AND platform_msg_id = ? AND role = 'user' LIMIT 1" - ).get(userId, platformName, msg.messageId); - if (already) { - console.warn(`[Messaging] Duplicate platform_msg_id ${msg.messageId} on ${platformName} for user ${userId} — skipping handlers`); - return { ...msg, agentId, platform: platformName }; - } + const enrichedMsg = { + agentId, + platform: platformName, + chatId: msg.chatId, + messageId: msg.messageId || null, + sender: msg.sender, + senderName: msg.senderName || null, + senderDisplayName: msg.senderDisplayName || null, + senderUsername: msg.senderUsername || null, + senderTag: msg.senderTag || null, + content: normalizedIncomingContent, + mediaType: msg.mediaType || null, + localMediaPath: msg.localMediaPath || null, + isGroup: msg.isGroup === true, + timestamp: msg.timestamp || new Date().toISOString(), + channelContext: Array.isArray(msg.channelContext) ? msg.channelContext.slice(-20) : null, + channelName: msg.channelName || null, + groupName: msg.groupName || null, + guildName: msg.guildName || null, + roomName: msg.roomName || null, + metadata: msg.metadata && typeof msg.metadata === 'object' ? msg.metadata : null, + }; + const queued = enqueueInboundMessage({ + userId, + agentId, + platform: platformName, + platformMessageId: msg.messageId || null, + chatId: msg.chatId, + content: normalizedIncomingContent, + metadata, + createdAt: enrichedMsg.timestamp, + payload: enrichedMsg, + }); + const durableMessage = queued.payload || payloadForInboundJob(queued.job) || enrichedMsg; + + if (this.isShuttingDown) { + return durableMessage; } - db.prepare('INSERT INTO messages (user_id, agent_id, role, content, platform, platform_msg_id, platform_chat_id, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)') - .run( - userId, - agentId, - 'user', - normalizedIncomingContent, - platformName, - msg.messageId, - msg.chatId, - JSON.stringify(metadata), - msg.timestamp, + if (queued.created) { + this.io.to(`user:${userId}`).emit('messaging:message', durableMessage); + } else if (!queued.job) { + console.warn( + `[Messaging] Duplicate ${platformName} message for user ${userId} predates durable processing state; skipping replay`, ); - - const enrichedMsg = { ...msg, content: normalizedIncomingContent, agentId, platform: platformName }; - - if (this.isShuttingDown) { - return enrichedMsg; + return durableMessage; + } else if (queued.job.status !== 'pending') { + console.warn( + `[Messaging] Duplicate ${platformName} message for user ${userId} has durable status ${queued.job.status}; skipping replay`, + ); + return durableMessage; } - this.io.to(`user:${userId}`).emit('messaging:message', enrichedMsg); + await this._processInboundJob(queued.job, durableMessage); + return durableMessage; + } + + async _processInboundJob(job, payload) { + if (this.isShuttingDown || !job || this.messageHandlers.length === 0) return false; + if (!claimInboundJob(job.id)) return false; + let finishTracking; + const tracked = new Promise((resolve) => { + finishTracking = resolve; + }); + this.activeInboundJobs.add(tracked); - for (const handler of this.messageHandlers) { - if (this.isShuttingDown) { - break; + let status = 'completed'; + let failure = null; + let runId = null; + try { + for (const handler of this.messageHandlers) { + if (this.isShuttingDown) { + status = 'pending'; + break; + } + let handlerResult = await handler(job.user_id, payload); + if (handlerResult?.completion) handlerResult = await handlerResult.completion; + const outcome = handlerResult?.outcome || handlerResult || {}; + runId = outcome.runId || runId; + if (outcome.cancelled === true) { + status = runId ? 'failed' : 'pending'; + failure = runId + ? 'The server stopped after this inbound agent run began; it will not be replayed automatically.' + : null; + break; + } + if (outcome.error) { + status = 'failed'; + failure = outcome.error; + break; + } } - try { - await handler(userId, enrichedMsg); - } catch (err) { - console.error('Message handler error:', err.message); + } catch (error) { + const cancelled = this.isShuttingDown + || isAbortError(error, this.lifecycleAbortController.signal); + status = cancelled && !runId ? 'pending' : 'failed'; + failure = status === 'failed' ? error : null; + if (!cancelled) { + console.error('[Messaging] Inbound message handler failed:', error?.message || error); } } - return enrichedMsg; + try { + settleInboundJob(job.id, status, failure?.message || failure || null); + return status === 'completed'; + } finally { + finishTracking(); + this.activeInboundJobs.delete(tracked); + } + } + + _platformReadyForInboundJob(job) { + const platform = this.platforms.get(this._key(job.user_id, job.agent_id, job.platform)); + if (!platform) return false; + try { + return String(platform.getStatus?.() || platform.status || '').toLowerCase() === 'connected'; + } catch { + return false; + } + } + + recoverPendingInbound(filters = {}) { + if (this.isShuttingDown || this.messageHandlers.length === 0) { + return Promise.resolve({ recovered: 0, skipped: 0 }); + } + const key = JSON.stringify({ + userId: filters.userId, + agentId: filters.agentId, + platform: filters.platform, + }); + if (this.activeInboundRecoveries.has(key)) return this.activeInboundRecoveries.get(key); + + const recovery = Promise.resolve().then(async () => { + if (!this.inboundJobsReconciled) { + reconcileInterruptedInboundJobs(); + this.inboundJobsReconciled = true; + } + let recovered = 0; + let skipped = 0; + for (const job of listPendingInboundJobs(filters)) { + if (this.isShuttingDown) break; + if (!this._platformReadyForInboundJob(job)) { + skipped += 1; + continue; + } + const payload = payloadForInboundJob(job); + if (!payload) { + settleInboundJob(job.id, 'failed', 'Stored inbound message payload is invalid.'); + continue; + } + if (await this._processInboundJob(job, payload)) recovered += 1; + } + return { recovered, skipped }; + }).finally(() => { + if (this.activeInboundRecoveries.get(key) === recovery) { + this.activeInboundRecoveries.delete(key); + } + }); + this.activeInboundRecoveries.set(key, recovery); + return recovery; } _agentId(userId, options = {}) { @@ -315,6 +500,8 @@ class MessagingManager extends EventEmitter { } async connectPlatform(userId, platformName, config = {}, options = {}) { + this._assertRunning(); + throwIfAborted(options.signal, 'Messaging platform connection aborted.'); const agentId = this._agentId(userId, options); config = { ...(config || {}) }; config.userId = userId; @@ -410,6 +597,16 @@ class MessagingManager extends EventEmitter { this.io.to(`user:${userId}`).emit('messaging:connected', { agentId, platform: platformName }); db.prepare('UPDATE platform_connections SET status = ?, last_connected = datetime(\'now\') WHERE user_id = ? AND agent_id = ? AND platform = ?') .run('connected', userId, agentId, platformName); + this.emit('platform_connected', { userId, agentId, platform: platformName }); + void this.recoverPendingInbound({ + userId, + agentId, + platform: platformName, + }).catch((error) => { + if (!this.isShuttingDown) { + console.error('[Messaging] Inbound recovery failed:', error?.message || error); + } + }); }); platform.on('disconnected', (info) => { @@ -455,9 +652,13 @@ class MessagingManager extends EventEmitter { }); }); - platform.on('message', async (msg) => { + platform.on('message', (msg) => { if (this.isShuttingDown) return; - await this.ingestMessage(userId, platformName, msg, { agentId }); + void this.ingestMessage(userId, platformName, msg, { agentId }).catch((error) => { + if (!this.isShuttingDown) { + console.error('[Messaging] Failed to persist or dispatch inbound message:', error?.message || error); + } + }); }); if (!existingConnection) { @@ -468,7 +669,19 @@ class MessagingManager extends EventEmitter { .run(storedConfig, 'connecting', userId, agentId, platformName); } - await platform.connect(); + try { + await this._runOperation( + options, + `${platformName} connection`, + () => platform.connect(), + ); + } catch (error) { + if (currentPlatform()) { + this.platforms.delete(key); + } + await Promise.resolve(platform.disconnect?.()).catch(() => {}); + throw error; + } return { status: platform.getStatus() }; } @@ -489,6 +702,7 @@ class MessagingManager extends EventEmitter { } async sendMessage(userId, platformName, to, content, mediaPathOrOptions) { + this._assertRunning(); const agentId = this._agentId(userId, mediaPathOrOptions || {}); const key = this._key(userId, agentId, platformName); const platform = this.platforms.get(key); @@ -505,6 +719,7 @@ class MessagingManager extends EventEmitter { ? sendOptions.metadata : null; const deliveryKind = sendOptions.deliveryKind || 'final'; + throwIfAborted(sendOptions.signal, 'Message delivery aborted.'); const normalizedContent = normalizeOutgoingMessageForPlatform(platformName, content, { stripNoResponseMarker: false }); @@ -514,7 +729,13 @@ class MessagingManager extends EventEmitter { return { success: true, suppressed: true }; } - const result = await platform.sendMessage(to, normalizedContent, sendOptions); + const result = await this._runOperation( + sendOptions, + `${platformName} message delivery`, + (signal) => platform.sendMessage(to, normalizedContent, { ...sendOptions, signal }), + ); + this._assertRunning(); + throwIfAborted(sendOptions.signal, 'Message delivery aborted.'); if (result?.success === false) { const reason = result.error || result.reason || 'platform rejected the message'; const error = new Error(`Platform ${platformName} delivery failed: ${reason}`); @@ -676,7 +897,7 @@ class MessagingManager extends EventEmitter { } async restoreConnections() { - this.isShuttingDown = false; + this._assertRunning(); const rows = db.prepare( "SELECT user_id, agent_id, platform, config FROM platform_connections WHERE status IN ('connected', 'awaiting_qr')" ).all(); @@ -715,17 +936,55 @@ class MessagingManager extends EventEmitter { } async shutdown() { + if (this.shutdownPromise) return this.shutdownPromise; this.isShuttingDown = true; - - const tasks = []; - for (const platform of this.platforms.values()) { - if (typeof platform.disconnect === 'function') { - tasks.push(platform.disconnect().catch(() => {})); + this.lifecycleAbortController.abort(messagingShutdownError()); + + this.shutdownPromise = (async () => { + const activeOperationTasks = Array.from(this.activeOperations, (operation) => + waitForBoundedResult(operation, { + serviceName: 'Messaging operation shutdown', + timeoutMs: 10000, + })); + activeOperationTasks.push(...Array.from(this.activeInboundJobs, (job) => + waitForBoundedResult(job, { + serviceName: 'Messaging inbound job shutdown', + timeoutMs: 10000, + }))); + activeOperationTasks.push(...Array.from(this.activeInboundRecoveries.values(), (recovery) => + waitForBoundedResult(recovery, { + serviceName: 'Messaging inbound recovery shutdown', + timeoutMs: 10000, + }))); + const disconnectTasks = []; + for (const platform of this.platforms.values()) { + if (typeof platform.disconnect === 'function') { + disconnectTasks.push(waitForBoundedResult( + Promise.resolve().then(() => platform.disconnect()), + { + serviceName: `${platform.name || 'Messaging platform'} disconnect`, + timeoutMs: 10000, + }, + )); + } } - } - await Promise.allSettled(tasks); - this.platforms.clear(); + const [operationResults, disconnectResults] = await Promise.all([ + Promise.allSettled(activeOperationTasks), + Promise.allSettled(disconnectTasks), + ]); + this.platforms.clear(); + return { + state: 'stopped', + cancelledOperationCount: operationResults.filter( + (result) => result.status === 'rejected', + ).length, + failedDisconnectCount: disconnectResults.filter( + (result) => result.status === 'rejected', + ).length, + }; + })(); + return this.shutdownPromise; } async makeCall(userId, to, greeting, options = {}) { @@ -733,23 +992,39 @@ class MessagingManager extends EventEmitter { const platform = this.platforms.get(key); if (!platform) throw new Error('Telnyx Voice is not connected'); if (!platform.initiateCall) throw new Error('Telnyx platform does not support outbound calls'); - const result = await platform.initiateCall(to, greeting); + const result = await this._runOperation( + options, + 'Telnyx outbound call', + (signal) => platform.initiateCall(to, greeting, { ...options, signal }), + ); this.io.to(`user:${userId}`).emit('messaging:call_initiated', { platform: 'telnyx', to, callControlId: result.callControlId }); return { success: true, ...result }; } async markRead(userId, platformName, chatId, messageId, options = {}) { + this._assertRunning(); const key = this._key(userId, this._agentId(userId, options), platformName); const platform = this.platforms.get(key); if (!platform?.markRead) return; - return platform.markRead(chatId, messageId); + return this._runOperation( + options, + `${platformName} read receipt`, + (signal) => platform.markRead(chatId, messageId, { ...options, signal }), + 15000, + ); } async sendTyping(userId, platformName, chatId, isTyping, options = {}) { + this._assertRunning(); const key = this._key(userId, this._agentId(userId, options), platformName); const platform = this.platforms.get(key); if (!platform?.sendTyping) return; - return platform.sendTyping(chatId, isTyping); + return this._runOperation( + options, + `${platformName} typing indicator`, + (signal) => platform.sendTyping(chatId, isTyping, { ...options, signal }), + 15000, + ); } /** diff --git a/server/services/messaging/typing_keepalive.js b/server/services/messaging/typing_keepalive.js index 5b19eb10..66cc21be 100644 --- a/server/services/messaging/typing_keepalive.js +++ b/server/services/messaging/typing_keepalive.js @@ -9,6 +9,7 @@ function startTypingKeepalive({ runId, platform, chatId, + signal = null, intervalMs = 4000, onError = null }) { @@ -65,7 +66,7 @@ function startTypingKeepalive({ platform, chatId, isTyping, - { agentId } + { agentId, signal } ); } catch (error) { reportFailure(operation, error); @@ -97,7 +98,9 @@ function startTypingKeepalive({ releaseWait = null; } await loop.catch((error) => reportFailure('typing keepalive loop', error)); - await sendTyping(false, 'clear typing indicator'); + if (!signal?.aborted) { + await sendTyping(false, 'clear typing indicator'); + } })(); return stopPromise; }; diff --git a/server/services/network/http.js b/server/services/network/http.js new file mode 100644 index 00000000..6415d807 --- /dev/null +++ b/server/services/network/http.js @@ -0,0 +1,210 @@ +'use strict'; + +const { createAbortError } = require('../../utils/abort'); + +const DEFAULT_HTTP_TIMEOUT_MS = 15000; +const DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024; + +function timeoutError(serviceName, timeoutMs, code = 'HTTP_TIMEOUT') { + const error = new Error(`${serviceName} request timed out after ${timeoutMs}ms.`); + error.code = code; + return error; +} + +function responseTooLargeError(serviceName, maxBytes, code = 'HTTP_RESPONSE_TOO_LARGE') { + const error = new Error( + `${serviceName} response exceeded the ${maxBytes}-byte safety limit.`, + ); + error.code = code; + return error; +} + +function waitForAbortableResult(promise, signal, fallback = 'Request aborted.') { + if (!signal) return Promise.resolve(promise); + if (signal.aborted) return Promise.reject(createAbortError(signal, fallback)); + + return new Promise((resolve, reject) => { + const onAbort = () => reject(createAbortError(signal, fallback)); + signal.addEventListener('abort', onAbort, { once: true }); + Promise.resolve(promise).then(resolve, reject).finally(() => { + signal.removeEventListener('abort', onAbort); + }); + }); +} + +async function waitForBoundedResult(promise, options = {}) { + const timeoutMs = Number(options.timeoutMs) > 0 + ? Number(options.timeoutMs) + : DEFAULT_HTTP_TIMEOUT_MS; + const serviceName = String(options.serviceName || 'Remote service').trim() + || 'Remote service'; + let timer = null; + const deadline = new Promise((_, reject) => { + timer = setTimeout(() => reject(timeoutError( + serviceName, + timeoutMs, + options.timeoutCode, + )), timeoutMs); + }); + try { + return await waitForAbortableResult( + Promise.race([Promise.resolve(promise), deadline]), + options.signal, + ); + } finally { + clearTimeout(timer); + } +} + +async function readResponseBuffer(response, options = {}) { + const maxBytes = Number(options.maxResponseBytes) > 0 + ? Number(options.maxResponseBytes) + : DEFAULT_MAX_RESPONSE_BYTES; + const serviceName = String(options.serviceName || 'Remote service').trim() + || 'Remote service'; + const tooLarge = () => responseTooLargeError( + serviceName, + maxBytes, + options.tooLargeCode, + ); + const signal = options.signal || null; + if (signal?.aborted) { + await response?.body?.cancel?.().catch(() => {}); + throw createAbortError(signal); + } + const contentLength = Number(response?.headers?.get?.('content-length')); + if (Number.isFinite(contentLength) && contentLength > maxBytes) { + await response?.body?.cancel?.().catch(() => {}); + throw tooLarge(); + } + + const reader = response?.body?.getReader?.(); + if (!reader) { + let buffer; + if (typeof response?.arrayBuffer === 'function') { + buffer = Buffer.from(await waitForAbortableResult(response.arrayBuffer(), signal)); + } else { + buffer = Buffer.from(await waitForAbortableResult(response.text(), signal), 'utf8'); + } + if (buffer.byteLength > maxBytes) throw tooLarge(); + return buffer; + } + + const chunks = []; + let totalBytes = 0; + const cancelReader = () => { + Promise.resolve(reader.cancel(signal?.reason)).catch(() => {}); + }; + signal?.addEventListener('abort', cancelReader, { once: true }); + try { + while (true) { + const { done, value } = await waitForAbortableResult(reader.read(), signal); + if (done) break; + const chunk = Buffer.from(value); + totalBytes += chunk.byteLength; + if (totalBytes > maxBytes) { + await reader.cancel().catch(() => {}); + throw tooLarge(); + } + chunks.push(chunk); + } + } finally { + signal?.removeEventListener('abort', cancelReader); + reader.releaseLock?.(); + } + return Buffer.concat(chunks, totalBytes); +} + +async function readResponseText(response, options = {}) { + return (await readResponseBuffer(response, options)).toString('utf8'); +} + +async function fetchResponse(url, options = {}, reader = readResponseBuffer) { + const serviceName = String(options.serviceName || 'Remote service').trim() + || 'Remote service'; + const timeoutMs = Number(options.timeoutMs) > 0 + ? Number(options.timeoutMs) + : DEFAULT_HTTP_TIMEOUT_MS; + const maxResponseBytes = Number(options.maxResponseBytes) > 0 + ? Number(options.maxResponseBytes) + : DEFAULT_MAX_RESPONSE_BYTES; + const callerSignal = options.signal || null; + const fetchImpl = typeof options.fetchImpl === 'function' ? options.fetchImpl : fetch; + if (callerSignal?.aborted) throw createAbortError(callerSignal); + + const controller = new AbortController(); + let rejectDeadline; + let timeoutFailure = null; + const deadline = new Promise((_, reject) => { + rejectDeadline = reject; + }); + const abortFromCaller = () => { + const error = createAbortError(callerSignal); + controller.abort(error); + rejectDeadline(error); + }; + callerSignal?.addEventListener('abort', abortFromCaller, { once: true }); + + const timer = setTimeout(() => { + timeoutFailure = timeoutError(serviceName, timeoutMs, options.timeoutCode); + controller.abort(timeoutFailure); + rejectDeadline(timeoutFailure); + }, timeoutMs); + + const { + fetchImpl: _fetchImpl, + maxResponseBytes: _maxResponseBytes, + serviceName: _serviceName, + signal: _signal, + timeoutCode: _timeoutCode, + timeoutMs: _timeoutMs, + tooLargeCode: _tooLargeCode, + ...fetchOptions + } = options; + const operation = Promise.resolve().then(async () => { + const response = await fetchImpl(url, { + ...fetchOptions, + signal: controller.signal, + }); + const body = await reader(response, { + maxResponseBytes, + serviceName, + signal: controller.signal, + tooLargeCode: options.tooLargeCode, + }); + return { response, body }; + }); + + try { + return await Promise.race([operation, deadline]); + } catch (error) { + if (callerSignal?.aborted) throw createAbortError(callerSignal); + if (timeoutFailure) throw timeoutFailure; + throw error; + } finally { + clearTimeout(timer); + callerSignal?.removeEventListener('abort', abortFromCaller); + } +} + +async function fetchResponseBuffer(url, options = {}) { + return fetchResponse(url, options, readResponseBuffer); +} + +async function fetchResponseText(url, options = {}) { + const { response, body } = await fetchResponse(url, options, readResponseBuffer); + return { response, text: body.toString('utf8') }; +} + +module.exports = { + DEFAULT_HTTP_TIMEOUT_MS, + DEFAULT_MAX_RESPONSE_BYTES, + fetchResponseBuffer, + fetchResponseText, + readResponseBuffer, + readResponseText, + responseTooLargeError, + timeoutError, + waitForAbortableResult, + waitForBoundedResult, +}; diff --git a/server/services/network/safe_request.js b/server/services/network/safe_request.js new file mode 100644 index 00000000..088885a4 --- /dev/null +++ b/server/services/network/safe_request.js @@ -0,0 +1,307 @@ +'use strict'; + +const dns = require('node:dns'); +const http = require('node:http'); +const https = require('node:https'); +const net = require('node:net'); +const { + createAbortError, + createLinkedAbortController, + throwIfAborted, +} = require('../../utils/abort'); +const { isPrivateHost } = require('../../utils/cloud-security'); + +const ALLOWED_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']); +const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]); +const SENSITIVE_REDIRECT_HEADERS = new Set([ + 'authorization', + 'cookie', + 'proxy-authorization', +]); +const HIDDEN_RESPONSE_HEADERS = new Set([ + 'proxy-authenticate', + 'set-cookie', + 'www-authenticate', +]); +const DEFAULT_TIMEOUT_MS = 30000; +const MAX_TIMEOUT_MS = 120000; +const MAX_RESPONSE_BYTES = 512 * 1024; +const MAX_REDIRECTS = 5; + +function timeoutError(timeoutMs) { + const error = new Error(`Request timed out after ${timeoutMs}ms.`); + error.code = 'HTTP_REQUEST_TIMEOUT'; + return error; +} + +function normalizeHeaders(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) return {}; + const headers = {}; + for (const [rawName, rawValue] of Object.entries(value)) { + const name = String(rawName || '').trim().toLowerCase(); + if (!name || rawValue == null) continue; + if (name === 'host' || name === 'connection' || name === 'transfer-encoding') continue; + headers[name] = Array.isArray(rawValue) + ? rawValue.map((item) => String(item)) + : String(rawValue); + } + return headers; +} + +function parseHttpUrl(value) { + let parsed; + try { + parsed = new URL(String(value || '').trim()); + } catch { + throw new Error('Invalid URL.'); + } + if (!['http:', 'https:'].includes(parsed.protocol)) { + throw new Error('URL scheme not allowed. Only http and https are permitted.'); + } + if (parsed.username || parsed.password) { + throw new Error('Credentials in request URLs are not permitted. Use headers instead.'); + } + return parsed; +} + +async function waitForLookup(lookupPromise, signal) { + throwIfAborted(signal, 'HTTP request aborted.'); + if (!signal) return lookupPromise; + return new Promise((resolve, reject) => { + const onAbort = () => reject(createAbortError(signal)); + signal.addEventListener('abort', onAbort, { once: true }); + Promise.resolve(lookupPromise).then(resolve, reject).finally(() => { + signal.removeEventListener('abort', onAbort); + }); + }); +} + +async function resolveHttpTarget(url, options = {}) { + const parsed = url instanceof URL ? url : parseHttpUrl(url); + const hostname = parsed.hostname.replace(/^\[|\]$/g, ''); + const allowPrivate = options.allowPrivate === true; + if (!allowPrivate && isPrivateHost(hostname)) { + throw new Error('Private, loopback, and reserved network addresses are not permitted.'); + } + if (net.isIP(hostname)) { + return { address: hostname, family: net.isIP(hostname), parsed }; + } + + const lookup = options.lookup || dns.promises.lookup; + let records; + try { + records = await waitForLookup( + lookup(hostname, { all: true, verbatim: true }), + options.signal, + ); + } catch (error) { + throwIfAborted(options.signal, 'HTTP request aborted.'); + throw new Error(`Could not resolve request host: ${error?.message || 'DNS lookup failed'}`); + } + const addresses = (Array.isArray(records) ? records : [records]) + .map((record) => ({ + address: String(record?.address || record || ''), + family: Number(record?.family) || net.isIP(record?.address || record), + })) + .filter((record) => record.address && record.family); + if (addresses.length === 0) throw new Error('Could not resolve request host.'); + if (!allowPrivate && addresses.some((record) => isPrivateHost(record.address))) { + throw new Error('Request host resolves to a private, loopback, or reserved address.'); + } + return { ...addresses[0], parsed }; +} + +function sanitizeResponseHeaders(headers = {}) { + const safe = {}; + for (const [name, value] of Object.entries(headers)) { + const normalized = String(name || '').toLowerCase(); + if (!normalized || HIDDEN_RESPONSE_HEADERS.has(normalized)) continue; + safe[normalized] = Array.isArray(value) ? value.join(', ') : String(value ?? ''); + } + return safe; +} + +function collectResponseBody(chunks, totalBytes, responseType) { + const buffer = Buffer.concat(chunks, totalBytes); + return responseType === 'buffer' ? buffer : buffer.toString('utf8'); +} + +function requestOnce(target, options = {}) { + const { parsed, address, family } = target; + const requestImpl = options.requestImpl + || (parsed.protocol === 'https:' ? https.request : http.request); + const headers = { ...normalizeHeaders(options.headers), host: parsed.host }; + if (options.body != null) { + headers['content-length'] = String(Buffer.byteLength(options.body)); + } else { + delete headers['content-length']; + } + + return new Promise((resolve, reject) => { + let settled = false; + const finish = (callback, value) => { + if (settled) return; + settled = true; + callback(value); + }; + const request = requestImpl({ + protocol: parsed.protocol, + hostname: address, + family, + port: parsed.port || undefined, + method: options.method, + path: `${parsed.pathname}${parsed.search}`, + headers, + maxHeaderSize: 32 * 1024, + servername: net.isIP(parsed.hostname.replace(/^\[|\]$/g, '')) + ? undefined + : parsed.hostname, + signal: options.signal, + }, (response) => { + const status = Number(response.statusCode) || 0; + if (REDIRECT_STATUSES.has(status) && response.headers?.location) { + response.destroy(); + finish(resolve, { status, headers: response.headers, body: '', truncated: false }); + return; + } + const chunks = []; + let totalBytes = 0; + let truncated = false; + response.on('data', (chunkValue) => { + if (settled) return; + const chunk = Buffer.from(chunkValue); + const remaining = options.maxResponseBytes - totalBytes; + if (remaining > 0) { + chunks.push(chunk.subarray(0, remaining)); + totalBytes += Math.min(chunk.byteLength, remaining); + } + if (chunk.byteLength > remaining) { + truncated = true; + response.destroy(); + finish(resolve, { + status, + headers: response.headers, + body: collectResponseBody(chunks, totalBytes, options.responseType), + truncated, + }); + } + }); + response.on('end', () => finish(resolve, { + status, + headers: response.headers, + body: collectResponseBody(chunks, totalBytes, options.responseType), + truncated, + })); + response.on('error', (error) => finish(reject, error)); + }); + request.on('error', (error) => finish(reject, error)); + if (options.body != null) request.write(options.body); + request.end(); + }); +} + +function redirectedRequest(previousUrl, nextUrl, method, headers, body, status) { + let nextMethod = method; + let nextBody = body; + const nextHeaders = { ...headers }; + if (status === 303 || ((status === 301 || status === 302) && method === 'POST')) { + nextMethod = 'GET'; + nextBody = null; + delete nextHeaders['content-length']; + delete nextHeaders['content-type']; + } + if (previousUrl.origin !== nextUrl.origin) { + for (const name of SENSITIVE_REDIRECT_HEADERS) delete nextHeaders[name]; + } + return { method: nextMethod, headers: nextHeaders, body: nextBody }; +} + +async function executeSafeHttpRequest(args = {}, context = {}) { + const method = String(args.method || 'GET').trim().toUpperCase(); + if (!ALLOWED_METHODS.has(method)) throw new Error(`Unsupported HTTP method: ${method}`); + const timeoutMs = Math.max( + 100, + Math.min(Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS), + ); + const timeoutController = new AbortController(); + const linked = createLinkedAbortController([context.signal, timeoutController.signal]); + const timer = setTimeout(() => timeoutController.abort(timeoutError(timeoutMs)), timeoutMs); + let currentUrl = parseHttpUrl(args.url); + let requestState = { + method, + headers: normalizeHeaders(args.headers), + body: args.body != null && !['GET', 'DELETE'].includes(method) + ? String(args.body) + : null, + }; + if (requestState.body != null && !requestState.headers['content-type']) { + requestState.headers['content-type'] = 'application/json'; + } + const redirects = []; + + try { + for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount += 1) { + throwIfAborted(linked.signal, 'HTTP request aborted.'); + const target = await resolveHttpTarget(currentUrl, { + allowPrivate: context.allowPrivate === true, + lookup: context.lookup, + signal: linked.signal, + }); + const response = await requestOnce(target, { + ...requestState, + maxResponseBytes: Number(context.maxResponseBytes) > 0 + ? Number(context.maxResponseBytes) + : MAX_RESPONSE_BYTES, + requestImpl: context.requestImpl, + responseType: context.responseType, + signal: linked.signal, + }); + const location = response.headers?.location; + if (!REDIRECT_STATUSES.has(response.status) || !location) { + return { + status: response.status, + headers: sanitizeResponseHeaders(response.headers), + body: response.truncated && typeof response.body === 'string' + ? `${response.body}\n...[truncated at response safety limit]` + : response.body, + finalUrl: currentUrl.toString(), + redirects, + truncated: response.truncated, + }; + } + if (redirectCount >= MAX_REDIRECTS) { + throw new Error(`Request exceeded the ${MAX_REDIRECTS}-redirect limit.`); + } + const nextUrl = parseHttpUrl(new URL(String(location), currentUrl).toString()); + redirects.push(nextUrl.toString()); + requestState = redirectedRequest( + currentUrl, + nextUrl, + requestState.method, + requestState.headers, + requestState.body, + response.status, + ); + currentUrl = nextUrl; + } + throw new Error(`Request exceeded the ${MAX_REDIRECTS}-redirect limit.`); + } catch (error) { + if (context.signal?.aborted) throw createAbortError(context.signal); + if (timeoutController.signal.aborted) throw createAbortError(timeoutController.signal); + throw error; + } finally { + clearTimeout(timer); + linked.cleanup(); + } +} + +module.exports = { + MAX_REDIRECTS, + MAX_RESPONSE_BYTES, + executeSafeHttpRequest, + normalizeHeaders, + parseHttpUrl, + redirectedRequest, + resolveHttpTarget, + sanitizeResponseHeaders, +}; diff --git a/server/services/runtime/backends/local-vm.js b/server/services/runtime/backends/local-vm.js index e7b32c6e..7ccefcef 100644 --- a/server/services/runtime/backends/local-vm.js +++ b/server/services/runtime/backends/local-vm.js @@ -1,6 +1,10 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const { DATA_DIR } = require('../../../../runtime/paths'); +const { fetchResponseText, waitForAbortableResult } = require('../../network/http'); +const { createAbortError } = require('../../../utils/abort'); const APK_UPLOAD_ROOT = path.resolve( process.env.NEOAGENT_ANDROID_APK_BASE_DIR @@ -13,6 +17,25 @@ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } +function abortError(signal, fallback = 'Operation aborted.') { + return createAbortError(signal, fallback); +} + +function delayWithSignal(ms, signal) { + if (signal?.aborted) return Promise.reject(abortError(signal)); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject(abortError(signal)); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + }); +} + function assertPathInside(baseDir, candidatePath, label) { const resolvedBase = path.resolve(baseDir); const resolvedCandidate = path.resolve(candidatePath); @@ -54,12 +77,16 @@ class RuntimeHttpClient { let lastError = null; while (Date.now() - startedAt < timeoutMs) { + if (options.signal?.aborted) throw abortError(options.signal); if (!checkLiveness()) { throw new Error('Guest runtime process exited unexpectedly during bootstrap.'); } const elapsed = Math.round((Date.now() - startedAt) / 1000); try { - const health = await this.request('GET', '/health', undefined, { timeoutMs: 2000 }); + const health = await this.request('GET', '/health', undefined, { + timeoutMs: 2000, + signal: options.signal, + }); if (health?.status === 'ok') { console.log(`[Runtime] Guest agent ready after ${elapsed}s`); return health; @@ -71,7 +98,7 @@ class RuntimeHttpClient { console.log(`[Runtime] Waiting for guest agent health... (${elapsed}s elapsed, last error: ${error.message})`); } } - await new Promise((resolve) => setTimeout(resolve, intervalMs)); + await delayWithSignal(intervalMs, options.signal); } if (lastError) { @@ -81,49 +108,59 @@ class RuntimeHttpClient { } async request(method, pathname, body, options = {}) { - const retryCount = Math.max(0, Number(options.retryCount ?? 6)); + const normalizedMethod = String(method || 'GET').toUpperCase(); + const safeToRetry = ['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod); + const retryCount = Math.max(0, Number(options.retryCount ?? (safeToRetry ? 6 : 0))); const retryDelayMs = Math.max(100, Number(options.retryDelayMs ?? 1000)); let lastError = null; for (let attempt = 0; attempt <= retryCount; attempt += 1) { - const controller = options.timeoutMs ? new AbortController() : null; - const timer = controller ? setTimeout(() => controller.abort(new Error(`Request timed out after ${options.timeoutMs} ms.`)), options.timeoutMs) : null; + if (options.signal?.aborted) throw abortError(options.signal); try { - const response = await fetch(`${this.baseUrl}${pathname}`, { - method, + const { response, text } = await fetchResponseText(`${this.baseUrl}${pathname}`, { + method: normalizedMethod, headers: { 'content-type': 'application/json', ...(this.token ? { authorization: `Bearer ${this.token}` } : {}), }, body: body === undefined ? undefined : JSON.stringify(body), - signal: controller?.signal, + signal: options.signal, + timeoutMs: Number(options.timeoutMs || 30000), + maxResponseBytes: Number(options.maxResponseBytes || 32 * 1024 * 1024), + serviceName: 'Guest runtime', + timeoutCode: 'RUNTIME_HTTP_TIMEOUT', + tooLargeCode: 'RUNTIME_HTTP_RESPONSE_TOO_LARGE', }); const contentType = response.headers.get('content-type') || ''; - const payload = contentType.includes('application/json') - ? await response.json().catch(() => ({})) - : { text: await response.text().catch(() => '') }; + let payload = { text }; + if (contentType.includes('application/json')) { + try { + payload = JSON.parse(text); + } catch { + payload = {}; + } + } if (!response.ok) { const errorMessage = payload?.error || payload?.text || `Runtime request failed: ${response.status}`; - throw new Error(errorMessage); + const error = new Error(errorMessage); + error.status = response.status; + throw error; } if (typeof this.onActivity === 'function') { this.onActivity(); } return payload; } catch (error) { + if (options.signal?.aborted) throw abortError(options.signal); lastError = error; const message = String(error?.message || error); const retryable = /fetch failed|ECONNREFUSED|ECONNRESET|socket hang up|timed out/i.test(message); if (!retryable || attempt === retryCount) { throw error; } - await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); - } finally { - if (timer) { - clearTimeout(timer); - } + await delayWithSignal(retryDelayMs, options.signal); } } @@ -131,13 +168,14 @@ class RuntimeHttpClient { } async requestStream(method, pathname, stream, options = {}) { - const retryCount = Math.max(0, Number(options.retryCount ?? 6)); + const retryCount = 0; const retryDelayMs = Math.max(100, Number(options.retryDelayMs ?? 1000)); let lastError = null; for (let attempt = 0; attempt <= retryCount; attempt += 1) { + if (options.signal?.aborted) throw abortError(options.signal); try { - const response = await fetch(`${this.baseUrl}${pathname}`, { + const { response, text } = await fetchResponseText(`${this.baseUrl}${pathname}`, { method, headers: { ...(options.contentType ? { 'content-type': options.contentType } : {}), @@ -147,6 +185,12 @@ class RuntimeHttpClient { }, body: stream, duplex: 'half', + signal: options.signal, + timeoutMs: Number(options.timeoutMs || 60000), + maxResponseBytes: Number(options.maxResponseBytes || 2 * 1024 * 1024), + serviceName: 'Guest runtime upload', + timeoutCode: 'RUNTIME_HTTP_TIMEOUT', + tooLargeCode: 'RUNTIME_HTTP_RESPONSE_TOO_LARGE', }); if (response.ok && typeof this.onActivity === 'function') { @@ -154,23 +198,31 @@ class RuntimeHttpClient { } const contentType = response.headers.get('content-type') || ''; - const payload = contentType.includes('application/json') - ? await response.json().catch(() => ({})) - : { text: await response.text().catch(() => '') }; + let payload = { text }; + if (contentType.includes('application/json')) { + try { + payload = JSON.parse(text); + } catch { + payload = {}; + } + } if (!response.ok) { const errorMessage = payload?.error || payload?.text || `Runtime request failed: ${response.status}`; - throw new Error(errorMessage); + const error = new Error(errorMessage); + error.status = response.status; + throw error; } return payload; } catch (error) { + if (options.signal?.aborted) throw abortError(options.signal); lastError = error; const message = String(error?.message || error); const retryable = /fetch failed|ECONNREFUSED|ECONNRESET|socket hang up|timed out/i.test(message); if (!retryable || attempt === retryCount) { throw error; } - await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); + await delayWithSignal(retryDelayMs, options.signal); } } @@ -186,7 +238,17 @@ class VmBrowserProvider { this.headless = true; } - async #materialize(result) { + async #request(method, pathname, body, options = {}) { + const payload = body && typeof body === 'object' ? { ...body } : body; + const signal = options.signal || payload?.signal || null; + if (payload && typeof payload === 'object') delete payload.signal; + return this.client.request(method, pathname, payload, { + timeoutMs: Number(options.timeoutMs || 120_000), + signal, + }); + } + + async #materialize(result, options = {}) { if (!result || !this.artifactStore || this.userId == null) { return result; } @@ -205,16 +267,18 @@ class VmBrowserProvider { let file = null; const maxAttempts = 20; for (let attempt = 0; attempt < maxAttempts && !file?.content; attempt += 1) { + if (options.signal?.aborted) throw abortError(options.signal); for (const candidate of readablePathCandidates) { try { - file = await this.client.request('POST', '/files/read', { + file = await this.#request('POST', '/files/read', { path: candidate, encoding: 'base64', - }); + }, { signal: options.signal, timeoutMs: 10_000 }); if (file?.content) { break; } } catch (error) { + if (options.signal?.aborted) throw abortError(options.signal); if (attempt === maxAttempts - 1) { console.warn('[Runtime:browser_vm] screenshot materialization read failed', { userId: this.userId, @@ -225,7 +289,7 @@ class VmBrowserProvider { } } if (!file?.content) { - await sleep(250); + await delayWithSignal(250, options.signal); } } if (!file?.content) { @@ -243,6 +307,7 @@ class VmBrowserProvider { } return result; } + if (options.signal?.aborted) throw abortError(options.signal); const allocation = this.artifactStore.allocateFile(this.userId, { kind: 'browser-screenshot', @@ -261,43 +326,87 @@ class VmBrowserProvider { }; } - async navigate(url, options = {}) { return this.#materialize(await this.client.request('POST', '/browser/navigate', { url, ...options })); } - async click(selector, text, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/click', { selector, text, screenshot })); } - async clickPoint(x, y, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/click-point', { x, y, screenshot })); } - async type(selector, text, options = {}) { return this.#materialize(await this.client.request('POST', '/browser/fill', { selector, value: text, ...options })); } - async typeText(text, options = {}) { return this.#materialize(await this.client.request('POST', '/browser/type-text', { text, ...options })); } - async pressKey(key, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/press-key', { key, screenshot })); } - async scroll(deltaX, deltaY, screenshot = true) { return this.#materialize(await this.client.request('POST', '/browser/scroll', { deltaX, deltaY, screenshot })); } - async extract(selector, attribute, all = false) { return this.client.request('POST', '/browser/extract', { selector, attribute, all }); } - async evaluate(script) { return this.client.request('POST', '/browser/execute', { code: script }); } - async screenshot(options = {}) { return this.#materialize(await this.client.request('POST', '/browser/screenshot', options)); } + async navigate(url, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/navigate', { url, ...options }, options), + options, + ); + } + async click(selector, text, screenshot = true, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/click', { selector, text, screenshot }, options), + options, + ); + } + async clickPoint(x, y, screenshot = true, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/click-point', { x, y, screenshot }, options), + options, + ); + } + async type(selector, text, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/fill', { selector, value: text, ...options }, options), + options, + ); + } + async typeText(text, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/type-text', { text, ...options }, options), + options, + ); + } + async pressKey(key, screenshot = true, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/press-key', { key, screenshot }, options), + options, + ); + } + async scroll(deltaX, deltaY, screenshot = true, options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/scroll', { deltaX, deltaY, screenshot }, options), + options, + ); + } + async extract(selector, attribute, all = false, options = {}) { + return this.#request('POST', '/browser/extract', { selector, attribute, all }, options); + } + async evaluate(script, options = {}) { + return this.#request('POST', '/browser/execute', { code: script }, options); + } + async screenshot(options = {}) { + return this.#materialize( + await this.#request('POST', '/browser/screenshot', options, options), + options, + ); + } async screenshotJpeg(quality = 80, options = {}) { - const result = await this.client.request('POST', '/browser/screenshot-jpeg', { ...options, quality }); + const result = await this.#request('POST', '/browser/screenshot-jpeg', { ...options, quality }, options); const content = String(result?.contentBase64 || ''); if (!content) throw new Error('VM browser screenshot-jpeg returned no data.'); return Buffer.from(content, 'base64'); } - async launch(options = {}) { return this.client.request('POST', '/browser/launch', options); } - async closeBrowser() { return this.client.request('POST', '/browser/close'); } - async fill(selector, value) { return this.type(selector, value); } - async extractContent(options = {}) { return this.client.request('POST', '/browser/extract', options); } - async executeJS(code) { return this.evaluate(code); } - async getPageInfo() { - const status = await this.client.request('GET', '/browser/status'); + async launch(options = {}) { return this.#request('POST', '/browser/launch', options, options); } + async closeBrowser(options = {}) { return this.#request('POST', '/browser/close', undefined, options); } + async fill(selector, value, options = {}) { return this.type(selector, value, options); } + async extractContent(options = {}) { return this.#request('POST', '/browser/extract', options, options); } + async executeJS(code, options = {}) { return this.evaluate(code, options); } + async getPageInfo(options = {}) { + const status = await this.client.request('GET', '/browser/status', undefined, options); this.headless = status?.headless !== false; return status?.pageInfo || null; } - async isLaunched() { - const status = await this.client.request('GET', '/browser/status'); + async isLaunched(options = {}) { + const status = await this.client.request('GET', '/browser/status', undefined, options); this.headless = status?.headless !== false; return status?.launched === true; } - async getPageCount() { - const status = await this.client.request('GET', '/browser/status'); + async getPageCount(options = {}) { + const status = await this.client.request('GET', '/browser/status', undefined, options); return Number(status?.pages || 0); } - async getCookies() { - return this.client.request('GET', '/browser/cookies'); + async getCookies(options = {}) { + return this.client.request('GET', '/browser/cookies', undefined, options); } async setHeadless(value) { this.headless = true; @@ -313,6 +422,9 @@ class LocalVmExecutionBackend { this.artifactStore = options.artifactStore || null; this.lastActivity = new Map(); this.reaperInterval = null; + this.reaperInFlight = false; + this.shuttingDown = false; + this.shutdownPromise = null; if (IDLE_TIMEOUT_MS > 0) { this.#startIdleReaper(); @@ -329,26 +441,48 @@ class LocalVmExecutionBackend { #startIdleReaper() { if (this.reaperInterval) return; this.reaperInterval = setInterval(async () => { + if (this.reaperInFlight || this.shuttingDown) return; + this.reaperInFlight = true; const now = Date.now(); - for (const [userId, lastUsed] of this.lastActivity.entries()) { - if (now - lastUsed > IDLE_TIMEOUT_MS) { - console.log(`[Runtime:${this.runtimeProfile}] User ${userId} runtime idle for ${Math.round((now - lastUsed) / 1000)}s, shutting down VM.`); - this.lastActivity.delete(userId); - try { - await this.vmManager?.killVm?.(userId); - } catch (err) { - console.error(`[Runtime:${this.runtimeProfile}] Failed to shut down idle VM for user ${userId}:`, err.message); + try { + for (const [userId, lastUsed] of this.lastActivity.entries()) { + if (now - lastUsed > IDLE_TIMEOUT_MS) { + console.log(`[Runtime:${this.runtimeProfile}] User ${userId} runtime idle for ${Math.round((now - lastUsed) / 1000)}s, shutting down VM.`); + this.lastActivity.delete(userId); + try { + await this.vmManager?.killVm?.(userId); + } catch (err) { + console.error(`[Runtime:${this.runtimeProfile}] Failed to shut down idle VM for user ${userId}:`, err.message); + } } } + } finally { + this.reaperInFlight = false; } }, Math.min(IDLE_TIMEOUT_MS, 60 * 1000)); + this.reaperInterval.unref?.(); } - async #clientForUser(userId) { + async #clientForUser(userId, options = {}) { + if (options.signal?.aborted) throw abortError(options.signal); + if (this.shuttingDown) { + const error = new Error('Local VM runtime is shutting down.'); + error.code = 'RUNTIME_SHUTTING_DOWN'; + throw error; + } if (!this.vmManager) { throw new Error('Local VM manager is not available.'); } - const session = await this.vmManager.ensureVm(userId); + const session = await waitForAbortableResult( + Promise.resolve(this.vmManager.ensureVm(userId, { signal: options.signal })), + options.signal, + 'VM startup aborted.', + ); + if (this.shuttingDown) { + const error = new Error('Local VM runtime is shutting down.'); + error.code = 'RUNTIME_SHUTTING_DOWN'; + throw error; + } this.#touch(userId); const client = new RuntimeHttpClient(session.baseUrl, session.guestToken || this.token, { onActivity: () => this.#touch(userId), @@ -356,6 +490,7 @@ class LocalVmExecutionBackend { try { await client.waitForHealth({ timeoutMs: Number(process.env.NEOAGENT_VM_BOOT_TIMEOUT_MS || 20 * 60 * 1000), + signal: options.signal, checkLiveness: () => { const key = String(userId || '').trim(); const session = this.vmManager.instances.get(key); @@ -363,6 +498,7 @@ class LocalVmExecutionBackend { }, }); } catch (error) { + if (options.signal?.aborted) throw abortError(options.signal); const runtimeError = typeof session.getLastError === 'function' ? session.getLastError() : ''; const detail = runtimeError ? ` ${runtimeError}` : ''; throw new Error(`${error.message}${detail}`.trim()); @@ -371,7 +507,11 @@ class LocalVmExecutionBackend { } async executeCommand(userId, command, options = {}) { - const client = await this.#clientForUser(userId); + const client = await this.#clientForUser(userId, options); + const requestedCommandTimeout = Number(options.timeout || 0); + const transportTimeout = requestedCommandTimeout > 0 + ? Math.min(30 * 60 * 1000, requestedCommandTimeout + 30_000) + : 16 * 60 * 1000; return client.request('POST', '/exec', { command, cwd: options.cwd, @@ -379,6 +519,10 @@ class LocalVmExecutionBackend { stdin_input: options.stdinInput, pty: options.pty === true, inputs: options.inputs || [], + }, { + timeoutMs: transportTimeout, + retryCount: 0, + signal: options.signal, }); } @@ -390,8 +534,8 @@ class LocalVmExecutionBackend { }); } - async getBrowserProviderForUser(userId) { - return new VmBrowserProvider(await this.#clientForUser(userId), { + async getBrowserProviderForUser(userId, options = {}) { + return new VmBrowserProvider(await this.#clientForUser(userId, options), { userId, artifactStore: this.artifactStore, }); @@ -431,11 +575,15 @@ class LocalVmExecutionBackend { } async shutdown() { + if (this.shutdownPromise) return this.shutdownPromise; + this.shuttingDown = true; if (this.reaperInterval) { clearInterval(this.reaperInterval); this.reaperInterval = null; } - await this.vmManager?.shutdown?.(); + this.shutdownPromise = Promise.resolve(this.vmManager?.shutdown?.()); + await this.shutdownPromise; + return { state: 'stopped' }; } } diff --git a/server/services/runtime/manager.js b/server/services/runtime/manager.js index fa94e801..b0af5158 100644 --- a/server/services/runtime/manager.js +++ b/server/services/runtime/manager.js @@ -16,6 +16,7 @@ class RuntimeManager { this.browserExtensionRegistry = options.browserExtensionRegistry || null; this.desktopCompanionRegistry = options.desktopCompanionRegistry || null; this.shellWorkerPool = options.shellWorkerPool || null; + this.androidControllers = new Map(); const browserVmManager = options.browserVmManager || new DockerVMManager({ runtimeProfile: 'browser_cli', @@ -44,6 +45,12 @@ class RuntimeManager { artifactStore: options.artifactStore, userId, })); + + this.createAndroidController = options.createAndroidController + || ((userId) => new AndroidController({ + userId, + artifactStore: this.artifactStore, + })); } @@ -91,14 +98,15 @@ class RuntimeManager { return this.browserBackend.getCommandExecutorForUser(userId); } - async getBrowserProviderForUser(userId) { + async getBrowserProviderForUser(userId, options = {}) { const settings = this.getSettings(userId); if (settings.browser_backend === 'extension' && this.hasActiveExtensionBrowser(userId)) { return this.getExtensionBrowserProvider(userId, { tokenId: settings.browser_extension_token_id, + signal: options.signal, }); } - return this.browserBackend.getBrowserProviderForUser(userId); + return this.browserBackend.getBrowserProviderForUser(userId, options); } async getCliProviderForUser(userId) { @@ -122,12 +130,6 @@ class RuntimeManager { async executeCliCommand(userId, command, options = {}) { const provider = await this.getCliProviderForUser(userId); - // Route desktop-companion shell commands through the isolated worker pool - // when available. The VM (Docker) path is already isolated. - if (provider.backend === 'desktop-companion' && this.shellWorkerPool && options.pty !== true) { - const result = await this.shellWorkerPool.execute(command, options); - return { ...result, backend: 'desktop-companion-worker' }; - } const result = await (options.pty === true && provider.executeInteractive ? provider.executeInteractive(command, options.inputs || [], options) : provider.execute(command, options)); @@ -146,10 +148,11 @@ class RuntimeManager { if (userId == null || String(userId).trim() === '') { throw new Error('Android provider requires a user ID.'); } - return new AndroidController({ - userId: String(userId).trim(), - artifactStore: this.artifactStore, - }); + const key = String(userId).trim(); + if (!this.androidControllers.has(key)) { + this.androidControllers.set(key, this.createAndroidController(key)); + } + return this.androidControllers.get(key); } async isGuestAgentReadyForUser(userId, timeoutMs = 1000, capability = 'browser') { @@ -162,7 +165,9 @@ class RuntimeManager { async shutdown() { const tasks = [ this.browserBackend?.shutdown?.(), + ...Array.from(this.androidControllers.values(), (controller) => controller?.close?.()), ]; + this.androidControllers.clear(); if (typeof this.shellWorkerPool?.shutdown === 'function') { tasks.push(Promise.resolve().then(() => this.shellWorkerPool.shutdown())); } diff --git a/server/services/social_reach/channels/github.js b/server/services/social_reach/channels/github.js index 5ae99e41..d677b5f1 100644 --- a/server/services/social_reach/channels/github.js +++ b/server/services/social_reach/channels/github.js @@ -28,9 +28,12 @@ class GithubChannel extends SocialReachChannel { }; } - async read({ url }) { + async read({ url, signal }) { const { owner, repo } = parseRepoUrl(url); - const data = await fetchJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`); + const data = await fetchJson( + `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, + { signal }, + ); return { platform: this.id, owner, @@ -47,14 +50,17 @@ class GithubChannel extends SocialReachChannel { }; } - async search({ query, limit }) { + async search({ query, limit, signal }) { const q = String(query || '').trim(); if (!q) { const error = new Error('query is required.'); error.status = 400; throw error; } - const data = await fetchJson(`https://api.github.com/search/repositories?q=${encodeURIComponent(q)}&per_page=${normalizeLimit(limit, 10, 30)}`); + const data = await fetchJson( + `https://api.github.com/search/repositories?q=${encodeURIComponent(q)}&per_page=${normalizeLimit(limit, 10, 30)}`, + { signal }, + ); return { platform: this.id, query: q, diff --git a/server/services/social_reach/channels/reddit.js b/server/services/social_reach/channels/reddit.js index 97d50eb6..f1498a47 100644 --- a/server/services/social_reach/channels/reddit.js +++ b/server/services/social_reach/channels/reddit.js @@ -67,8 +67,8 @@ class RedditChannel extends SocialReachChannel { }; } - async read({ url, limit }) { - const data = await fetchJson(postUrlForJson(url)); + async read({ url, limit, signal }) { + const data = await fetchJson(postUrlForJson(url), { signal }); const post = shapePost(data?.[0]?.data?.children?.[0]); const comments = (data?.[1]?.data?.children || []) .filter((item) => item?.kind === 't1') @@ -82,7 +82,7 @@ class RedditChannel extends SocialReachChannel { }; } - async search({ query, limit }) { + async search({ query, limit, signal }) { const q = String(query || '').trim(); if (!q) { const error = new Error('query is required.'); @@ -90,7 +90,7 @@ class RedditChannel extends SocialReachChannel { throw error; } const url = `https://www.reddit.com/search.json?q=${encodeURIComponent(q)}&limit=${normalizeLimit(limit, 10, 50)}&raw_json=1`; - const data = await fetchJson(url); + const data = await fetchJson(url, { signal }); return { platform: this.id, query: q, diff --git a/server/services/social_reach/channels/rss.js b/server/services/social_reach/channels/rss.js index 58f6a8af..a048bd65 100644 --- a/server/services/social_reach/channels/rss.js +++ b/server/services/social_reach/channels/rss.js @@ -31,9 +31,9 @@ class RssChannel extends SocialReachChannel { }; } - async read({ url, limit }) { + async read({ url, limit, signal }) { const parsed = assertHttpUrl(url); - const xml = await fetchText(parsed.toString()); + const xml = await fetchText(parsed.toString(), { publicOnly: true, signal }); const $ = cheerio.load(xml, { xmlMode: true }); const entries = $('item, entry').toArray().slice(0, normalizeLimit(limit, 20, 100)); const title = $('channel > title, feed > title').first().text().trim() || null; diff --git a/server/services/social_reach/channels/social_video.js b/server/services/social_reach/channels/social_video.js index 14fd73b9..3e306ede 100644 --- a/server/services/social_reach/channels/social_video.js +++ b/server/services/social_reach/channels/social_video.js @@ -3,6 +3,7 @@ const { getPlatformDefinition } = require('../platforms'); const { SocialReachChannel } = require('./base'); const { normalizeAndDetectPlatform } = require('../../social_video/url'); +const { createAbortError } = require('../../../utils/abort'); const PLATFORM_LABELS = Object.freeze({ youtube: 'YouTube', @@ -26,7 +27,7 @@ class SocialVideoReachChannel extends SocialReachChannel { } } - async check() { + async check({ signal } = {}) { if (!this.socialVideoService || typeof this.socialVideoService.getHealthStatus !== 'function') { return { ...(await super.check()), @@ -37,11 +38,14 @@ class SocialVideoReachChannel extends SocialReachChannel { }; } - const health = await this.socialVideoService.getHealthStatus().catch((error) => ({ - ready: false, - dependencies: [], - error: error.message || String(error), - })); + const health = await this.socialVideoService.getHealthStatus({ signal }).catch((error) => { + if (signal?.aborted) throw createAbortError(signal); + return { + ready: false, + dependencies: [], + error: error.message || String(error), + }; + }); const missing = (health.dependencies || []) .filter((item) => !item.available) .map((item) => item.name) @@ -59,7 +63,7 @@ class SocialVideoReachChannel extends SocialReachChannel { }; } - async read({ userId, url, include_frame: includeFrame, force_stt: forceStt, agentId }) { + async read({ userId, url, include_frame: includeFrame, force_stt: forceStt, agentId, signal }) { if (!this.socialVideoService || typeof this.socialVideoService.extractFromUrl !== 'function') { const error = new Error('Social video extraction is not connected right now.'); error.status = 503; @@ -70,6 +74,7 @@ class SocialVideoReachChannel extends SocialReachChannel { includeFrame: includeFrame !== false, forceStt: forceStt === true, agentId: agentId || null, + signal, }); return { ...result, diff --git a/server/services/social_reach/channels/v2ex.js b/server/services/social_reach/channels/v2ex.js index 130f4a43..f5fff9b4 100644 --- a/server/services/social_reach/channels/v2ex.js +++ b/server/services/social_reach/channels/v2ex.js @@ -3,6 +3,7 @@ const { SocialReachChannel } = require('./base'); const { getPlatformDefinition } = require('../platforms'); const { assertHttpUrl, fetchJson, normalizeLimit } = require('../utils'); +const { createAbortError } = require('../../../utils/abort'); function shapeTopic(item = {}) { const node = item.node || {}; @@ -33,17 +34,26 @@ class V2exChannel extends SocialReachChannel { }; } - async read({ url, limit }) { + async read({ url, limit, signal }) { const parsed = assertHttpUrl(url); const match = parsed.pathname.match(/^\/t\/(\d+)/); if (!match) { - return this.search({ query: parsed.searchParams.get('q') || 'hot', limit }); + return this.search({ query: parsed.searchParams.get('q') || 'hot', limit, signal }); } const topicId = match[1]; - const topicData = await fetchJson(`https://www.v2ex.com/api/topics/show.json?id=${encodeURIComponent(topicId)}`); + const topicData = await fetchJson( + `https://www.v2ex.com/api/topics/show.json?id=${encodeURIComponent(topicId)}`, + { signal }, + ); const topic = Array.isArray(topicData) ? topicData[0] || {} : topicData || {}; - const replies = await fetchJson(`https://www.v2ex.com/api/replies/show.json?topic_id=${encodeURIComponent(topicId)}&page=1`) - .catch(() => []); + const replies = await fetchJson( + `https://www.v2ex.com/api/replies/show.json?topic_id=${encodeURIComponent(topicId)}&page=1`, + { signal }, + ) + .catch((error) => { + if (signal?.aborted) throw createAbortError(signal); + return []; + }); return { platform: this.id, ...shapeTopic(topic), @@ -58,11 +68,14 @@ class V2exChannel extends SocialReachChannel { }; } - async search({ query, limit }) { + async search({ query, limit, signal }) { const normalized = String(query || '').trim().toLowerCase(); const capped = normalizeLimit(limit, 20, 100); if (normalized && normalized !== 'hot') { - const data = await fetchJson(`https://www.v2ex.com/api/topics/show.json?node_name=${encodeURIComponent(normalized)}&page=1`); + const data = await fetchJson( + `https://www.v2ex.com/api/topics/show.json?node_name=${encodeURIComponent(normalized)}&page=1`, + { signal }, + ); return { platform: this.id, query: normalized, @@ -70,7 +83,7 @@ class V2exChannel extends SocialReachChannel { source: 'v2ex_public_api', }; } - const data = await fetchJson('https://www.v2ex.com/api/topics/hot.json'); + const data = await fetchJson('https://www.v2ex.com/api/topics/hot.json', { signal }); return { platform: this.id, query: 'hot', diff --git a/server/services/social_reach/channels/x.js b/server/services/social_reach/channels/x.js index 0bca5d32..68938602 100644 --- a/server/services/social_reach/channels/x.js +++ b/server/services/social_reach/channels/x.js @@ -43,7 +43,7 @@ class XChannel extends SocialReachChannel { }; } - async read({ userId, url }) { + async read({ userId, url, signal }) { const id = extractStatusId(url); if (!id) { const error = new Error('A public X post URL is required.'); @@ -54,7 +54,7 @@ class XChannel extends SocialReachChannel { const source = cookieHeaderForPlatform(userId, this.id) ? 'x_syndication_cookies' : 'x_syndication_public'; const data = await fetchJson( `https://cdn.syndication.twimg.com/tweet-result?id=${encodeURIComponent(id)}&lang=en`, - { headers: this.#headers(userId) }, + { headers: this.#headers(userId), signal }, ); return { platform: this.id, diff --git a/server/services/social_reach/channels/xueqiu.js b/server/services/social_reach/channels/xueqiu.js index 2cfb45ce..d9cede69 100644 --- a/server/services/social_reach/channels/xueqiu.js +++ b/server/services/social_reach/channels/xueqiu.js @@ -37,7 +37,7 @@ class XueqiuChannel extends SocialReachChannel { }; } - async search({ userId, query, limit }) { + async search({ userId, query, limit, signal }) { const q = String(query || '').trim(); if (!q) { const error = new Error('query is required.'); @@ -45,7 +45,7 @@ class XueqiuChannel extends SocialReachChannel { throw error; } const url = `https://xueqiu.com/stock/search.json?code=${encodeURIComponent(q)}&size=${normalizeLimit(limit, 10, 50)}`; - const data = await fetchJson(url, { headers: this.#headers(userId) }); + const data = await fetchJson(url, { headers: this.#headers(userId), signal }); return { platform: this.id, query: q, @@ -58,12 +58,12 @@ class XueqiuChannel extends SocialReachChannel { }; } - async read({ userId, symbol, limit }) { + async read({ userId, symbol, limit, signal }) { const raw = String(symbol || '').trim().toUpperCase(); if (raw) { const data = await fetchJson( `https://stock.xueqiu.com/v5/stock/batch/quote.json?symbol=${encodeURIComponent(raw)}`, - { headers: this.#headers(userId) }, + { headers: this.#headers(userId), signal }, ); const quote = data.data?.items?.[0]?.quote || {}; return { @@ -76,7 +76,7 @@ class XueqiuChannel extends SocialReachChannel { } const data = await fetchJson( 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=-1&count=20&category=-1', - { headers: this.#headers(userId) }, + { headers: this.#headers(userId), signal }, ); return { platform: this.id, diff --git a/server/services/social_reach/service.js b/server/services/social_reach/service.js index 552f4a98..7a074dc4 100644 --- a/server/services/social_reach/service.js +++ b/server/services/social_reach/service.js @@ -5,6 +5,7 @@ const { createChannels } = require('./channels'); const { deleteCookieBundle, getCookieSummary, writeCookieBundle } = require('./store'); const { domainsForPlatform, getPlatformDefinition, normalizePlatformId } = require('./platforms'); const { assertHttpUrl } = require('./utils'); +const { createAbortError } = require('../../utils/abort'); const COOKIE_IMPORT_PLATFORMS = new Set(['xueqiu', 'x']); const MAX_IMPORTED_COOKIES = 80; @@ -59,16 +60,17 @@ class SocialReachService { return this.channels.find((channel) => channel.canHandleUrl(parsed.toString())) || null; } - async getStatus(userId) { + async getStatus(userId, options = {}) { const statuses = []; for (const channel of this.channels) { try { - const status = await channel.check({ userId }); + const status = await channel.check({ userId, signal: options.signal }); if (!status.cookie && channel.setupKind === 'cookies') { status.cookie = getCookieSummary(userId, channel.id); } statuses.push(status); } catch (error) { + if (options.signal?.aborted) throw createAbortError(options.signal); statuses.push({ platform: channel.id, label: channel.label, @@ -87,7 +89,7 @@ class SocialReachService { }; } - async read(userId, args = {}) { + async read(userId, args = {}, options = {}) { const platform = normalizePlatformId(args.platform || ''); const channel = platform ? this.getChannel(platform) : this.detectChannelForUrl(args.url); if (!channel) { @@ -97,10 +99,10 @@ class SocialReachService { error.status = 400; throw error; } - return channel.read({ ...args, userId }); + return channel.read({ ...args, userId, signal: options.signal }); } - async search(userId, args = {}) { + async search(userId, args = {}, options = {}) { const platform = normalizePlatformId(args.platform || ''); const channel = this.getChannel(platform); if (!channel) { @@ -108,7 +110,7 @@ class SocialReachService { error.status = 400; throw error; } - return channel.search({ ...args, userId }); + return channel.search({ ...args, userId, signal: options.signal }); } async importCookiesFromExtension(userId, platform, options = {}) { @@ -132,6 +134,7 @@ class SocialReachService { }, { tokenId: options.tokenId || null, timeoutMs: 15000, + signal: options.signal, }); const cookies = sanitizeCookies(response?.cookies || [], domains); if (cookies.length === 0) { diff --git a/server/services/social_reach/utils.js b/server/services/social_reach/utils.js index efa43756..d2db9a32 100644 --- a/server/services/social_reach/utils.js +++ b/server/services/social_reach/utils.js @@ -1,9 +1,13 @@ 'use strict'; const cheerio = require('cheerio'); +const { fetchResponseText } = require('../network/http'); +const { executeSafeHttpRequest } = require('../network/safe_request'); const DEFAULT_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'; +const DEFAULT_TIMEOUT_MS = 20000; +const MAX_RESPONSE_BYTES = 2 * 1024 * 1024; function normalizeLimit(value, fallback = 10, max = 50) { const n = Number(value); @@ -31,19 +35,60 @@ function assertHttpUrl(value) { } async function fetchText(url, options = {}) { - const response = await fetch(url, { - redirect: 'follow', - ...options, - headers: { - 'user-agent': DEFAULT_UA, - accept: 'text/plain,text/html,application/xml,application/rss+xml,application/atom+xml,*/*', - ...(options.headers || {}), - }, - }); - const text = await response.text(); - if (!response.ok) { - const error = new Error(`Request failed with HTTP ${response.status}`); - error.status = response.status; + const { + lookup, + maxResponseBytes = MAX_RESPONSE_BYTES, + publicOnly = false, + requestImpl, + signal, + timeoutMs = DEFAULT_TIMEOUT_MS, + ...requestOptions + } = options; + const headers = { + 'user-agent': DEFAULT_UA, + accept: 'text/plain,text/html,application/xml,application/rss+xml,application/atom+xml,*/*', + ...(options.headers || {}), + }; + let status; + let text; + if (publicOnly) { + const result = await executeSafeHttpRequest({ + url, + method: requestOptions.method || 'GET', + headers, + body: requestOptions.body, + timeout_ms: timeoutMs, + }, { + signal, + lookup, + requestImpl, + maxResponseBytes, + }); + if (result.truncated) { + const error = new Error('Response exceeded the Social Reach safety limit.'); + error.code = 'SOCIAL_REACH_RESPONSE_TOO_LARGE'; + error.status = 502; + throw error; + } + status = result.status; + text = result.body; + } else { + const result = await fetchResponseText(url, { + ...requestOptions, + headers, + signal, + timeoutMs, + maxResponseBytes, + serviceName: 'Social Reach', + timeoutCode: 'SOCIAL_REACH_TIMEOUT', + tooLargeCode: 'SOCIAL_REACH_RESPONSE_TOO_LARGE', + }); + status = result.response.status; + text = result.text; + } + if (status < 200 || status >= 300) { + const error = new Error(`Request failed with HTTP ${status}`); + error.status = status; error.body = text.slice(0, 500); throw error; } @@ -58,7 +103,13 @@ async function fetchJson(url, options = {}) { ...(options.headers || {}), }, }); - return JSON.parse(text); + try { + return JSON.parse(text); + } catch (cause) { + const error = new Error('Social Reach returned malformed JSON.', { cause }); + error.status = 502; + throw error; + } } function htmlToText(html, maxChars = 20000) { diff --git a/server/services/social_video/service.js b/server/services/social_video/service.js index 6bae1679..e648b76b 100644 --- a/server/services/social_video/service.js +++ b/server/services/social_video/service.js @@ -8,19 +8,28 @@ const { randomUUID } = require('crypto'); const db = require('../../db/database'); const { DATA_DIR } = require('../../../runtime/paths'); const { CLIExecutor } = require('../cli/executor'); +const { executeSafeHttpRequest } = require('../network/safe_request'); const { getAdapterForPlatform } = require('./adapters'); -const { decideTranscriptPath, parseCaptionText, pickCaptionTrack } = require('./captions'); +const { + MAX_VTT_BYTES, + decideTranscriptPath, + parseCaptionText, + pickCaptionTrack, +} = require('./captions'); const { inferImageContentType, pickDeterministicFrameSecond } = require('./frame'); const { extractPublicMetadataFromHtml } = require('./metadata'); const { shapeSocialVideoResult } = require('./result'); const { normalizeAndDetectPlatform } = require('./url'); const { isMainAgent } = require('../agents/manager'); const { resolveSttModel, transcribeVoiceInput } = require('../voice/providers'); +const { createAbortError, isAbortError, throwIfAborted } = require('../../utils/abort'); const SOCIAL_VIDEO_TMP_DIR = path.join(DATA_DIR, 'social-video-temp'); fs.mkdirSync(SOCIAL_VIDEO_TMP_DIR, { recursive: true }); const HEALTH_CACHE_TTL_MS = 5 * 60 * 1000; +const MAX_PAGE_HTML_BYTES = 2 * 1024 * 1024; +const MAX_THUMBNAIL_BYTES = 10 * 1024 * 1024; // A realistic desktop browser UA. Social platforms frequently return 403/bot // challenge pages to the default Node/yt-dlp user agents, so we present a @@ -44,17 +53,38 @@ const YT_DLP_NETWORK_FLAGS = [ // bot detection. For these we always try to attach the user's browser cookies. const COOKIE_ASSISTED_PLATFORMS = new Set(['instagram', 'youtube', 'tiktok', 'x']); -async function fetchWithBrowserHeaders(url, options = {}) { - return fetch(url, { - redirect: 'follow', - ...options, +async function fetchPublicResource(url, options = {}) { + const result = await executeSafeHttpRequest({ + url, + method: 'GET', + timeout_ms: options.timeoutMs || 30000, headers: { 'user-agent': BROWSER_USER_AGENT, 'accept-language': 'en-US,en;q=0.9', - accept: '*/*', + accept: options.accept || '*/*', ...(options.headers || {}), }, + }, { + signal: options.signal, + maxResponseBytes: options.maxResponseBytes, + responseType: options.responseType, }); + if (result.truncated) { + const error = new Error('Social video response exceeded its safety limit.'); + error.code = 'SOCIAL_VIDEO_RESPONSE_TOO_LARGE'; + throw error; + } + if (result.status < 200 || result.status >= 300) { + const error = new Error(`Social video request failed (${result.status}).`); + error.status = result.status; + throw error; + } + return result; +} + +function rethrowCancellation(error, signal) { + if (signal?.aborted) throw createAbortError(signal); + if (isAbortError(error)) throw error; } function shellEscape(value) { @@ -256,6 +286,7 @@ class SocialVideoService { } async getHealthStatus(options = {}) { + throwIfAborted(options.signal, 'Social video health check aborted.'); const forceRefresh = options.forceRefresh === true; const now = Date.now(); if (!forceRefresh && this._healthCache.value && (now - this._healthCache.ts) < HEALTH_CACHE_TTL_MS) { @@ -263,8 +294,8 @@ class SocialVideoService { } const [ytDlp, ffmpeg] = await Promise.all([ - this.#probeBinary(this.ytDlpBin, '--version'), - this.#probeBinary(this.ffmpegBin, '-version'), + this.#probeBinary(this.ytDlpBin, '--version', options.signal), + this.#probeBinary(this.ffmpegBin, '-version', options.signal), ]); const health = { @@ -291,7 +322,8 @@ class SocialVideoService { let jobDir = null; try { - const health = await this.getHealthStatus(); + throwIfAborted(options.signal, 'Social video extraction aborted.'); + const health = await this.getHealthStatus({ signal: options.signal }); if (!health.ready) { const missing = health.dependencies.filter((item) => !item.available).map((item) => item.name); throw new Error(`Missing required dependency: ${missing.join(', ')}`); @@ -303,16 +335,28 @@ class SocialVideoService { throw new Error(`No adapter registered for platform: ${platform}`); } - const pageMetadata = await this.#resolvePageMetadata(userId, normalizedUrl, warnings); + const pageMetadata = await this.#resolvePageMetadata( + userId, + normalizedUrl, + warnings, + options.signal, + ); + throwIfAborted(options.signal, 'Social video extraction aborted.'); jobDir = await fsp.mkdtemp(path.join(SOCIAL_VIDEO_TMP_DIR, `${platform}-${Date.now()}-`)); const cookieFilePath = await this.#resolveCookieFile({ userId, platform, jobDir, warnings, + signal: options.signal, }); - const mediaInfo = await this.#readMediaInfo(normalizedUrl, jobDir, cookieFilePath); + const mediaInfo = await this.#readMediaInfo( + normalizedUrl, + jobDir, + cookieFilePath, + options.signal, + ); const baseTitle = String(pageMetadata.title || mediaInfo.title || '').trim(); const baseDescription = String(pageMetadata.description || mediaInfo.description || '').trim(); const resolvedUrl = String(pageMetadata.resolvedUrl || mediaInfo.webpage_url || normalizedUrl).trim(); @@ -339,6 +383,7 @@ class SocialVideoService { userId, agentId, warnings, + signal: options.signal, }); const frameImage = options.includeFrame === false @@ -350,6 +395,7 @@ class SocialVideoService { jobDir, cookieFilePath, warnings, + signal: options.signal, }); return shapeSocialVideoResult({ @@ -372,7 +418,8 @@ class SocialVideoService { errors, }); } catch (error) { - const health = await this.getHealthStatus().catch(() => null); + rethrowCancellation(error, options.signal); + const health = await this.getHealthStatus({ signal: options.signal }).catch(() => null); errors.push(classifyExtractionError(error)); return shapeSocialVideoResult({ sourceUrl: source, @@ -403,14 +450,16 @@ class SocialVideoService { cwd: options.cwd || process.cwd(), timeout: options.timeout || 10 * 60 * 1000, env: options.env, + signal: options.signal, }); + throwIfAborted(options.signal, 'Social video command aborted.'); if (result.exitCode !== 0) { throw new Error(result.stderr || result.stdout || `Command failed: ${command}`); } return result; } - async #probeBinary(binary, versionFlag) { + async #probeBinary(binary, versionFlag, signal = null) { const name = String(binary || '').trim(); const fallback = { name, @@ -430,7 +479,9 @@ class SocialVideoService { const command = `${shellEscape(name)} ${versionFlag}`; const result = await this.cliExecutor.execute(command, { timeout: 8 * 1000, + signal, }); + throwIfAborted(signal, 'Social video dependency probe aborted.'); if (result.exitCode !== 0) { return { ...fallback, @@ -447,6 +498,7 @@ class SocialVideoService { error: null, }; } catch (error) { + rethrowCancellation(error, signal); return { ...fallback, error: error.message || String(error), @@ -454,8 +506,13 @@ class SocialVideoService { } } - async #resolvePageMetadata(userId, normalizedUrl, warnings) { - const browserMetadata = await this.#resolvePageMetadataViaBrowser(userId, normalizedUrl).catch((error) => { + async #resolvePageMetadata(userId, normalizedUrl, warnings, signal = null) { + const browserMetadata = await this.#resolvePageMetadataViaBrowser( + userId, + normalizedUrl, + signal, + ).catch((error) => { + rethrowCancellation(error, signal); warnings.push(`Browser metadata resolve failed: ${error.message}`); return null; }); @@ -463,21 +520,24 @@ class SocialVideoService { return browserMetadata; } - const response = await fetchWithBrowserHeaders(normalizedUrl); - const html = await response.text(); - const metadata = extractPublicMetadataFromHtml(html, response.url || normalizedUrl); + const response = await fetchPublicResource(normalizedUrl, { + signal, + maxResponseBytes: MAX_PAGE_HTML_BYTES, + accept: 'text/html,*/*', + }); + const metadata = extractPublicMetadataFromHtml(response.body, response.finalUrl || normalizedUrl); return { ...metadata, - resolvedUrl: String(response.url || normalizedUrl), + resolvedUrl: String(response.finalUrl || normalizedUrl), }; } - async #resolvePageMetadataViaBrowser(userId, normalizedUrl) { + async #resolvePageMetadataViaBrowser(userId, normalizedUrl, signal = null) { if (!this.runtimeManager || typeof this.runtimeManager.getBrowserProviderForUser !== 'function') { throw new Error('Runtime browser provider is unavailable.'); } - const browser = await this.runtimeManager.getBrowserProviderForUser(userId); + const browser = await this.runtimeManager.getBrowserProviderForUser(userId, { signal }); if (!browser || typeof browser.navigate !== 'function' || typeof browser.extract !== 'function') { throw new Error('Runtime browser provider does not support metadata extraction.'); } @@ -485,16 +545,29 @@ class SocialVideoService { const nav = await browser.navigate(normalizedUrl, { screenshot: false, waitUntil: 'domcontentloaded', + signal, }); if (nav?.error) { throw new Error(nav.error); } const [canonicalRaw, descriptionRaw, ogDescriptionRaw, titleTagRaw] = await Promise.all([ - browser.extract('link[rel="canonical"]', 'href', false).catch(() => ''), - browser.extract('meta[name="description"]', 'content', false).catch(() => ''), - browser.extract('meta[property="og:description"]', 'content', false).catch(() => ''), - browser.extract('meta[property="og:title"]', 'content', false).catch(() => ''), + browser.extract('link[rel="canonical"]', 'href', false, { signal }).catch((error) => { + rethrowCancellation(error, signal); + return ''; + }), + browser.extract('meta[name="description"]', 'content', false, { signal }).catch((error) => { + rethrowCancellation(error, signal); + return ''; + }), + browser.extract('meta[property="og:description"]', 'content', false, { signal }).catch((error) => { + rethrowCancellation(error, signal); + return ''; + }), + browser.extract('meta[property="og:title"]', 'content', false, { signal }).catch((error) => { + rethrowCancellation(error, signal); + return ''; + }), ]); const canonical = unwrapBrowserExtractValue(canonicalRaw); const description = unwrapBrowserExtractValue(descriptionRaw); @@ -509,16 +582,17 @@ class SocialVideoService { }; } - async #readMediaInfo(normalizedUrl, jobDir, cookieFilePath = null) { + async #readMediaInfo(normalizedUrl, jobDir, cookieFilePath = null, signal = null) { const infoTemplate = path.join(jobDir, 'media.%(ext)s'); const infoPath = path.join(jobDir, 'media.info.json'); const cookieArg = cookieFilePath ? ` --cookies ${shellEscape(cookieFilePath)}` : ''; const command = `${shellEscape(this.ytDlpBin)} --quiet --no-warnings --no-playlist ${this.#networkFlags()} --skip-download --write-info-json --no-clean-infojson${cookieArg} -o ${shellEscape(infoTemplate)} -- ${shellEscape(normalizedUrl)}`; - await this.#runCommand(command, { cwd: jobDir, timeout: 4 * 60 * 1000 }); + await this.#runCommand(command, { cwd: jobDir, timeout: 4 * 60 * 1000, signal }); if (!fileExists(infoPath)) { throw new Error('yt-dlp did not produce an info JSON artifact.'); } const raw = String(await fsp.readFile(infoPath, 'utf8')).trim(); + throwIfAborted(signal, 'Social video extraction aborted.'); let parsed; try { parsed = JSON.parse(raw); @@ -530,7 +604,11 @@ class SocialVideoService { async #resolveTranscript(context) { if (context.transcriptDecision.mode === 'captions' && context.captionTrack) { - const captionText = await this.#readTranscriptFromCaption(context.captionTrack).catch((error) => { + const captionText = await this.#readTranscriptFromCaption( + context.captionTrack, + context.signal, + ).catch((error) => { + rethrowCancellation(error, context.signal); context.warnings.push(`Caption transcript failed: ${error.message}`); return ''; }); @@ -544,6 +622,7 @@ class SocialVideoService { } const transcript = await this.#transcribeViaStt(context).catch((error) => { + rethrowCancellation(error, context.signal); context.warnings.push(`Speech-to-text fallback failed: ${error.message}`); return ''; }); @@ -553,20 +632,24 @@ class SocialVideoService { }; } - async #readTranscriptFromCaption(captionTrack) { - const response = await fetchWithBrowserHeaders(captionTrack.url); - if (!response.ok) { - throw new Error(`Caption request failed (${response.status}).`); - } - const raw = await response.text(); - return parseCaptionText(raw, captionTrack.ext); + async #readTranscriptFromCaption(captionTrack, signal = null) { + const response = await fetchPublicResource(captionTrack.url, { + signal, + maxResponseBytes: MAX_VTT_BYTES, + accept: 'text/vtt,text/plain,application/json,application/xml,*/*', + }); + return parseCaptionText(response.body, captionTrack.ext); } async #transcribeViaStt(context) { const template = path.join(context.jobDir, 'audio.%(ext)s'); const cookieArg = context.cookieFilePath ? ` --cookies ${shellEscape(context.cookieFilePath)}` : ''; const command = `${shellEscape(this.ytDlpBin)} --quiet --no-warnings --no-playlist ${this.#networkFlags()}${cookieArg} -o ${shellEscape(template)} -f bestaudio/best -- ${shellEscape(context.sourceUrl)}`; - await this.#runCommand(command, { cwd: context.jobDir, timeout: 10 * 60 * 1000 }); + await this.#runCommand(command, { + cwd: context.jobDir, + timeout: 10 * 60 * 1000, + signal: context.signal, + }); const audioPath = firstFileMatching(context.jobDir, 'audio.'); if (!audioPath || !fileExists(audioPath)) { @@ -576,10 +659,12 @@ class SocialVideoService { const sttConfig = await Promise.resolve( this.voiceSettingsResolver(context.userId, context.agentId), ); + throwIfAborted(context.signal, 'Social video transcription aborted.'); return this.voiceTranscriber(audioPath, { provider: sttConfig?.provider || 'openai', model: sttConfig?.model || '', mimeType: detectMimeFromFile(audioPath), + signal: context.signal, }); } @@ -599,13 +684,19 @@ class SocialVideoService { } const browser = await Promise.resolve( - this.runtimeManager.getBrowserProviderForUser(context.userId), - ).catch(() => null); + this.runtimeManager.getBrowserProviderForUser(context.userId, { + signal: context.signal, + }), + ).catch((error) => { + rethrowCancellation(error, context.signal); + return null; + }); if (!browser || typeof browser.getCookies !== 'function') { return null; } - const payload = await browser.getCookies().catch((error) => { + const payload = await browser.getCookies({ signal: context.signal }).catch((error) => { + rethrowCancellation(error, context.signal); context.warnings.push(`Browser cookie export failed: ${error.message}`); return null; }); @@ -619,11 +710,13 @@ class SocialVideoService { const cookieFilePath = path.join(context.jobDir, 'browser.cookies.txt'); await fsp.writeFile(cookieFilePath, serializeCookiesForNetscapeJar(cookies), 'utf8'); + throwIfAborted(context.signal, 'Social video cookie export aborted.'); return cookieFilePath; } async #resolveFrameImage(context) { const downloadedFrame = await this.#extractFrameFromVideo(context).catch((error) => { + rethrowCancellation(error, context.signal); context.warnings.push(`Frame extraction failed: ${error.message}`); return null; }); @@ -636,14 +729,22 @@ class SocialVideoService { context.warnings.push('No thumbnail fallback was available after frame extraction failed.'); return null; } - return this.#downloadThumbnailArtifact(context.userId, thumbnail.url); + return this.#downloadThumbnailArtifact( + context.userId, + thumbnail.url, + context.signal, + ); } async #extractFrameFromVideo(context) { const template = path.join(context.jobDir, 'video.%(ext)s'); const cookieArg = context.cookieFilePath ? ` --cookies ${shellEscape(context.cookieFilePath)}` : ''; const downloadCommand = `${shellEscape(this.ytDlpBin)} --quiet --no-warnings --no-playlist ${this.#networkFlags()}${cookieArg} -o ${shellEscape(template)} -f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/best" --merge-output-format mp4 -- ${shellEscape(context.sourceUrl)}`; - await this.#runCommand(downloadCommand, { cwd: context.jobDir, timeout: 14 * 60 * 1000 }); + await this.#runCommand(downloadCommand, { + cwd: context.jobDir, + timeout: 14 * 60 * 1000, + signal: context.signal, + }); const videoPath = firstFileMatching(context.jobDir, 'video.'); if (!videoPath || !fileExists(videoPath)) { @@ -653,7 +754,11 @@ class SocialVideoService { const framePath = path.join(context.jobDir, 'frame.jpg'); const frameSecond = pickDeterministicFrameSecond(context.mediaInfo.duration); const frameCommand = `${shellEscape(this.ffmpegBin)} -hwaccel none -y -hide_banner -loglevel error -ss ${frameSecond} -i ${shellEscape(videoPath)} -frames:v 1 -q:v 2 ${shellEscape(framePath)}`; - await this.#runCommand(frameCommand, { cwd: context.jobDir, timeout: 2 * 60 * 1000 }); + await this.#runCommand(frameCommand, { + cwd: context.jobDir, + timeout: 2 * 60 * 1000, + signal: context.signal, + }); if (!fileExists(framePath)) { throw new Error('ffmpeg did not produce a frame image.'); @@ -661,14 +766,19 @@ class SocialVideoService { return this.#saveImageArtifact(context.userId, framePath, 'frame'); } - async #downloadThumbnailArtifact(userId, thumbnailUrl) { - const response = await fetchWithBrowserHeaders(thumbnailUrl); - if (!response.ok) { - throw new Error(`Thumbnail request failed (${response.status}).`); - } - const buffer = Buffer.from(await response.arrayBuffer()); - const guessedExtension = path.extname(new URL(response.url || thumbnailUrl).pathname).replace('.', '') || 'jpg'; - const mimeType = String(response.headers.get('content-type') || '').trim() || `image/${guessedExtension}`; + async #downloadThumbnailArtifact(userId, thumbnailUrl, signal = null) { + const response = await fetchPublicResource(thumbnailUrl, { + signal, + maxResponseBytes: MAX_THUMBNAIL_BYTES, + responseType: 'buffer', + accept: 'image/*,*/*', + }); + const buffer = response.body; + const guessedExtension = path.extname( + new URL(response.finalUrl || thumbnailUrl).pathname, + ).replace('.', '') || 'jpg'; + const mimeType = String(response.headers['content-type'] || '').trim() + || `image/${guessedExtension}`; if (!this.artifactStore || userId == null) { return { url: null, diff --git a/server/services/tasks/integration_runtime.js b/server/services/tasks/integration_runtime.js index d876daac..6945df91 100644 --- a/server/services/tasks/integration_runtime.js +++ b/server/services/tasks/integration_runtime.js @@ -16,7 +16,14 @@ function sortByTimestamp(left, right) { return String(left.timestamp).localeCompare(String(right.timestamp)); } -async function fetchTriggerRows({ integrationManager, userId, agentId, triggerType, config }) { +async function fetchTriggerRows({ + integrationManager, + userId, + agentId, + triggerType, + config, + signal = null, +}) { if (!integrationManager) return []; const scopedAgentId = resolveAgentId(userId, agentId); const connectionArg = { @@ -36,7 +43,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy maxResults: 20, q: queryParts.join(' ').trim() || undefined, }, - }, scopedAgentId); + }, scopedAgentId, { signal }); const messages = Array.isArray(result?.messages) ? result.messages : []; return messages .map((item) => ({ @@ -62,7 +69,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy ...(filters.length ? { '$filter': filters.join(' and ') } : {}), ...(escapedQuery ? { '$search': `"${escapedQuery}"` } : {}), }, - }, scopedAgentId); + }, scopedAgentId, { signal }); const messages = Array.isArray(result?.value) ? result.value : []; return messages .map((item) => ({ @@ -85,7 +92,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy ...connectionArg, channel: config.channel, limit: 20, - }, scopedAgentId); + }, scopedAgentId, { signal }); const messages = Array.isArray(result?.result?.messages) ? result.result.messages : Array.isArray(result?.messages) @@ -115,7 +122,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy method: 'GET', path: `/v1.0/me/chats/${encodeURIComponent(config.chatId)}/messages`, query: { '$top': 20 }, - }, scopedAgentId); + }, scopedAgentId, { signal }); const messages = Array.isArray(result?.value) ? result.value : []; return messages .filter((item) => { @@ -143,7 +150,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy ...connectionArg, chat_id: config.chatId, limit: 25, - }, scopedAgentId); + }, scopedAgentId, { signal }); const messages = Array.isArray(result?.messages) ? result.messages : []; return messages .filter((item) => item && item.fromMe !== true) @@ -172,7 +179,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy ...connectionArg, ...(config.location ? { location: config.location } : {}), forecast_hours: Math.max(1, Math.min(Number(config.horizonHours) || 12, 48)), - }, scopedAgentId); + }, scopedAgentId, { signal }); const hourly = Array.isArray(forecast?.hourly) ? forecast.hourly : []; const eventTypes = Array.isArray(config.eventTypes) ? config.eventTypes : []; const rows = []; @@ -254,7 +261,7 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy return []; } -async function pollIntegrationTask(runtime, task) { +async function pollIntegrationTask(runtime, task, options = {}) { const config = normalizeJsonObject(task.trigger_config); const rows = await fetchTriggerRows({ integrationManager: runtime.integrationManager, @@ -262,6 +269,7 @@ async function pollIntegrationTask(runtime, task) { agentId: task.agent_id, triggerType: task.trigger_type, config, + signal: options.signal, }); if (!rows.length) return; @@ -314,8 +322,10 @@ function attachIntegrationEventSources(runtime) { const provider = runtime.integrationManager?.getProvider?.('whatsapp_personal'); if (provider && typeof provider.on === 'function') { const listener = async (event) => { + if (runtime.stopping) return; const tasks = runtime.taskRepository.listEnabledWhatsappEventTasks(event.userId, event.agentId); for (const task of tasks) { + if (runtime.stopping) break; if (!matchesWhatsappTaskEvent(task, event)) continue; await runtime.fireTaskFromTrigger(task.id, task.user_id, createWhatsappTriggerPayload(event)).catch((error) => { const logger = runtime.logger?.error || console.error; diff --git a/server/services/tasks/runtime.js b/server/services/tasks/runtime.js index 877ff485..eca0fcdc 100644 --- a/server/services/tasks/runtime.js +++ b/server/services/tasks/runtime.js @@ -15,6 +15,7 @@ const scheduleAdapter = require('./adapters/schedule'); const { normalizeJsonObject } = require('./utils'); const { normalizeOutgoingMessageForPlatform } = require('../messaging/formatting_guides'); const { isTransientError } = require('../ai/providerRetry'); +const { isAbortError, throwIfAborted } = require('../../utils/abort'); const MAX_AUTONOMOUS_RETRIES = 1; const MAX_RECURRING_TASK_START_DELAY_MS = 90 * 1000; @@ -112,6 +113,7 @@ class TaskRuntime { this.runningTaskExecutions = new Set(); this.activeExecutionPromises = new Set(); this.activePolls = new Map(); + this.abortController = new AbortController(); this.integrationEventCleanups = []; this.triggerRegistry = new TriggerRegistry(taskAdapters); this.started = false; @@ -149,6 +151,9 @@ class TaskRuntime { throw new Error('Task runtime cannot start while shutdown is in progress.'); } + if (this.abortController.signal.aborted) { + this.abortController = new AbortController(); + } this.started = true; this.stopping = false; this.state = 'starting'; @@ -208,6 +213,7 @@ class TaskRuntime { this.started = false; this.stopping = true; this.state = 'stopping'; + this.abortController.abort('Task runtime is stopping.'); this._stopScheduling(); this.stopPromise = (async () => { @@ -301,6 +307,9 @@ class TaskRuntime { runTaskNow(taskId, userId) { const task = this.taskRepository.getTaskById(taskId, userId); if (!task) throw new Error('Task not found'); + if (this.stopping || this.abortController.signal.aborted) { + return { running: false, skipped: true, reason: 'runtime_stopping' }; + } void this._executeTask(taskId, userId, { scheduledAt: new Date().toISOString(), manual: true, @@ -349,6 +358,7 @@ class TaskRuntime { const due = this.taskRepository.listDueOneTimeTasks(); for (const task of due) { + if (this.abortController.signal.aborted) break; this.scheduleJobs.delete(task.id); try { const result = await this._executeTask(task.id, task.user_id, { @@ -373,9 +383,13 @@ class TaskRuntime { return this._runPoll('integration', async () => { const tasks = this.taskRepository.listEnabledByTriggerTypes(POLLED_TRIGGER_TYPES); for (const task of tasks) { + if (this.abortController.signal.aborted) break; try { - await pollIntegrationTask(this, task); + await pollIntegrationTask(this, task, { + signal: this.abortController.signal, + }); } catch (error) { + if (isAbortError(error, this.abortController.signal) && this.stopping) break; console.error(`[Tasks] Trigger poll failed for task ${task.id}:`, error.message); } } @@ -390,13 +404,16 @@ class TaskRuntime { if (active) { return active; } - if (this.stopping) { + if (this.stopping || this.abortController.signal.aborted) { return Promise.resolve({ skipped: true, reason: 'runtime_stopping' }); } const promise = Promise.resolve() - .then(callback) + .then(() => callback(this.abortController.signal)) .catch((error) => { + if (isAbortError(error, this.abortController.signal) && this.stopping) { + return { skipped: true, reason: 'runtime_stopping' }; + } this.lastError = error.message; onError(error); return { error: error.message }; @@ -453,7 +470,7 @@ class TaskRuntime { } async _executeTask(taskId, userId, executionMeta = {}) { - if (this.stopping) { + if (this.stopping || this.abortController.signal.aborted) { return { skipped: true, reason: 'runtime_stopping' }; } const executionKey = `${userId}:${taskId}`; @@ -488,6 +505,8 @@ class TaskRuntime { } async _executeTaskSerial(taskId, userId, executionMeta = {}) { + const signal = this.abortController.signal; + if (signal.aborted) return { skipped: true, reason: 'runtime_stopping' }; const task = this.taskRepository.getTaskById(taskId, userId); if (!task || !task.enabled) return { skipped: true, reason: 'missing_or_disabled' }; @@ -582,7 +601,11 @@ class TaskRuntime { taskId, manual: executionMeta.manual === true, scheduledAt: executionMeta.scheduledAt || null, + signal, }); + if (this.stopping || signal.aborted) { + return { skipped: true, reason: 'runtime_stopping' }; + } this.io.to(`user:${userId}`).emit('tasks:task_complete', { taskId, result }); this._recordTaskLifecycle({ userId, @@ -647,12 +670,16 @@ class TaskRuntime { skipVerifier: false, stream: false, context: executionMeta.triggerPayload || {}, + signal, }; try { const result = typeof this.agentEngine.runWithModel === 'function' ? await this.agentEngine.runWithModel(userId, finalPrompt, runOptions, normalizedConfig.model || null) : await this.agentEngine.run(userId, finalPrompt, runOptions); completedRunId = result?.runId || null; + if (this.stopping || signal.aborted) { + return { skipped: true, reason: 'runtime_stopping', runId: completedRunId }; + } const fallbackDelivery = await this._deliverTaskResultIfNeeded({ userId, agentId, @@ -692,6 +719,9 @@ class TaskRuntime { }); return result; } catch (err) { + if (isAbortError(err, signal) && this.stopping) { + return { skipped: true, reason: 'runtime_stopping', runId: completedRunId }; + } const transientExecutionError = isTransientError(err); if (completedRunId && !transientExecutionError) { this.taskRepository.markAgentRunFailed(completedRunId, userId, err.message); @@ -733,6 +763,9 @@ class TaskRuntime { } } } catch (err) { + if (isAbortError(err, signal) && this.stopping) { + return { skipped: true, reason: 'runtime_stopping', runId: completedRunId }; + } console.error(`[Tasks] Task ${taskId} error:`, err.message); if (err?.code !== 'TASK_DELIVERY_FAILED') { const failureMessage = this._buildTaskFailureMessage(taskName, err); @@ -1045,6 +1078,7 @@ class TaskRuntime { deliveryState, allowPlainResultFallback = true, }) { + throwIfAborted(this.abortController.signal, 'Task runtime is stopping.'); if (deliveryState?.messagingSent || deliveryState?.noResponse || taskConfig.callTo) return null; const targets = this._buildNotifyTargets(userId, agentId, taskConfig); if (!targets.length) return null; @@ -1118,6 +1152,7 @@ class TaskRuntime { mediaPath: target.mediaPath || null, runId: result?.runId || null, persistConversation: true, + signal: this.abortController.signal, }); deliveryState.messagingSent = true; deliveryState.proactiveMessageStaged = false; diff --git a/server/services/voice/agentBridge.js b/server/services/voice/agentBridge.js index 1f989d13..2ac5acd4 100644 --- a/server/services/voice/agentBridge.js +++ b/server/services/voice/agentBridge.js @@ -3,6 +3,7 @@ const { randomUUID } = require('crypto'); const { runVoiceTranscriptTurn } = require('./turnRunner'); +const { createAbortError, throwIfAborted } = require('../../utils/abort'); class VoiceAgentBridge { constructor({ agentEngine, memoryManager, runtimeManager }) { @@ -20,6 +21,8 @@ class VoiceAgentBridge { await session.publishTranscriptFinal(transcriptText); await session.setState('thinking'); const runId = randomUUID(); + let ownedRunId = runId; + const turnSignal = session.signal; session.currentRunId = runId; try { @@ -40,8 +43,11 @@ class VoiceAgentBridge { allowInterimUpdates: true, voiceSessionId: session.id, runId, + signal: turnSignal, }); - session.currentRunId = result.runId || runId; + throwIfAborted(turnSignal, 'Voice turn was interrupted.'); + ownedRunId = result.runId || runId; + session.currentRunId = ownedRunId; const replyText = String(result.replyText || '').trim(); if (replyText) { if (deferredFollowUp) { @@ -50,6 +56,7 @@ class VoiceAgentBridge { deferredFollowUp, replyText, result.runId || runId, + { signal: turnSignal }, ); if (!followUp?.sent) { await this.runtimeManager.deliverAssistantMessage(session, replyText, { @@ -62,12 +69,18 @@ class VoiceAgentBridge { }); } } + throwIfAborted(turnSignal, 'Voice turn was interrupted.'); await session.setState('idle'); - session.currentRunId = null; + if (session.currentRunId === ownedRunId) { + session.currentRunId = null; + } return result; } catch (error) { - session.currentRunId = null; - await session.setState('idle'); + if (session.currentRunId === ownedRunId) session.currentRunId = null; + if (session.signal === turnSignal && !session.closed) { + await session.setState('idle'); + } + if (turnSignal.aborted) throw createAbortError(turnSignal); throw error; } } diff --git a/server/services/voice/bufferedLiveRelayAdapter.js b/server/services/voice/bufferedLiveRelayAdapter.js index 9b857752..c7f99444 100644 --- a/server/services/voice/bufferedLiveRelayAdapter.js +++ b/server/services/voice/bufferedLiveRelayAdapter.js @@ -3,6 +3,7 @@ const { getProviderRuntimeConfig } = require('../ai/models'); const { resolveSttModel, transcribeVoiceInput } = require('./providers'); const { writeTempAudioFile, removeTempFile } = require('./liveAudio'); +const { createAbortError } = require('../../utils/abort'); const DEFAULT_PARTIAL_DEBOUNCE_MS = 700; const DEFAULT_MIN_PARTIAL_BYTES = 8000; @@ -68,6 +69,7 @@ class BufferedLiveRelayAdapter { userId: session.userId, agentId: session.agentId, timeoutMs: 20000, + signal: session.signal, }); } finally { // Release buffered audio immediately after commit so completed turns do @@ -99,6 +101,7 @@ class BufferedLiveRelayAdapter { userId: session.userId, agentId: session.agentId, timeoutMs: 6000, + signal: session.signal, }); if (transcript) { await session.publishTranscriptPartial(transcript); @@ -136,9 +139,11 @@ class BufferedLiveRelayAdapter { apiKey: attempt.apiKey, baseUrl: attempt.baseUrl, timeoutMs: options.timeoutMs, + signal: options.signal, }); return String(transcript || '').trim(); } catch (error) { + if (options.signal?.aborted) throw createAbortError(options.signal); lastError = error; } } diff --git a/server/services/voice/liveSession.js b/server/services/voice/liveSession.js index 42c2e2b3..36be91c3 100644 --- a/server/services/voice/liveSession.js +++ b/server/services/voice/liveSession.js @@ -1,5 +1,12 @@ 'use strict'; +function voiceAbortError(message, code) { + const error = new Error(message); + error.name = 'AbortError'; + error.code = code; + return error; +} + class VoiceLiveSession { constructor({ id, @@ -32,6 +39,11 @@ class VoiceLiveSession { this.lastAssistantText = ''; this.assistantMessageCount = 0; this.closed = false; + this.turnAbortController = new AbortController(); + } + + get signal() { + return this.turnAbortController.signal; } resetInput(mimeType = 'audio/pcm;rate=16000;channels=1') { @@ -46,6 +58,13 @@ class VoiceLiveSession { } resetTurnState() { + if (!this.turnAbortController.signal.aborted) { + this.turnAbortController.abort(voiceAbortError( + 'Voice turn was reset.', + 'VOICE_TURN_RESET', + )); + } + this.turnAbortController = new AbortController(); this.lastPartialTranscript = ''; this.lastFinalTranscript = ''; this.lastAssistantText = ''; @@ -204,6 +223,12 @@ class VoiceLiveSession { async interruptOutput() { this.interrupted = true; + if (!this.turnAbortController.signal.aborted) { + this.turnAbortController.abort(voiceAbortError( + 'Voice output was interrupted.', + 'VOICE_INTERRUPTED', + )); + } if (typeof this.sink?.interruptOutput === 'function') { await this.sink.interruptOutput(this); } @@ -217,6 +242,12 @@ class VoiceLiveSession { async close(reason = 'closed') { this.closed = true; + if (!this.turnAbortController.signal.aborted) { + this.turnAbortController.abort(voiceAbortError( + `Voice session closed: ${reason}.`, + 'VOICE_SESSION_CLOSED', + )); + } if (typeof this.sink?.close === 'function') { await this.sink.close(this, reason); } diff --git a/server/services/voice/openaiSpeech.js b/server/services/voice/openaiSpeech.js index 14511040..7421b92e 100644 --- a/server/services/voice/openaiSpeech.js +++ b/server/services/voice/openaiSpeech.js @@ -1,9 +1,22 @@ 'use strict'; +const { readResponseBuffer } = require('../network/http'); +const { runWithAbortTimeout } = require('../../utils/abort'); + +const DEFAULT_SPEECH_TIMEOUT_MS = 30000; +const DEFAULT_MAX_SPEECH_BYTES = 32 * 1024 * 1024; + async function synthesizeSpeechBuffer( client, text, - { model = 'gpt-4o-mini-tts', voice = 'alloy', responseFormat = 'mp3' } = {}, + { + model = 'gpt-4o-mini-tts', + voice = 'alloy', + responseFormat = 'mp3', + signal = null, + timeoutMs = DEFAULT_SPEECH_TIMEOUT_MS, + maxResponseBytes = DEFAULT_MAX_SPEECH_BYTES, + } = {}, ) { if (!client) { throw new Error('OpenAI client is not configured for speech synthesis.'); @@ -14,14 +27,26 @@ async function synthesizeSpeechBuffer( throw new Error('Speech input is empty; cannot synthesize audio.'); } - const response = await client.audio.speech.create({ - model: String(model || 'gpt-4o-mini-tts').trim() || 'gpt-4o-mini-tts', - voice: String(voice || 'alloy').trim() || 'alloy', - input: content, - response_format: String(responseFormat || 'mp3').trim() || 'mp3', - }); + return runWithAbortTimeout(async (operationSignal) => { + const response = await client.audio.speech.create({ + model: String(model || 'gpt-4o-mini-tts').trim() || 'gpt-4o-mini-tts', + voice: String(voice || 'alloy').trim() || 'alloy', + input: content, + response_format: String(responseFormat || 'mp3').trim() || 'mp3', + }, { signal: operationSignal }); - return Buffer.from(await response.arrayBuffer()); + return readResponseBuffer(response, { + maxResponseBytes, + serviceName: 'OpenAI speech synthesis', + signal: operationSignal, + tooLargeCode: 'VOICE_PROVIDER_RESPONSE_TOO_LARGE', + }); + }, { + signal, + timeoutMs, + timeoutCode: 'VOICE_PROVIDER_TIMEOUT', + label: 'OpenAI speech synthesis', + }); } module.exports = { diff --git a/server/services/voice/providers.js b/server/services/voice/providers.js index 79525d84..4c793677 100644 --- a/server/services/voice/providers.js +++ b/server/services/voice/providers.js @@ -6,6 +6,12 @@ const { AGENT_DATA_DIR } = require('../../../runtime/paths'); const { getOpenAiClient } = require('./openaiClient'); const { synthesizeSpeechBuffer } = require('./openaiSpeech'); const { decryptLocalValue } = require('../../utils/local_secrets'); +const { fetchResponseBuffer, fetchResponseText } = require('../network/http'); +const { + createAbortError, + runWithAbortTimeout, + throwIfAborted, +} = require('../../utils/abort'); const DEEPGRAM_STT_MODEL = process.env.DEEPGRAM_MODEL || 'nova-3'; const DEEPGRAM_STT_LANGUAGE = process.env.DEEPGRAM_LANGUAGE || 'multi'; @@ -16,6 +22,7 @@ async function transcribeChunkWithDeepgram({ mimeType, detectLanguage = DEEPGRAM_STT_LANGUAGE, model = DEEPGRAM_STT_MODEL, + signal = null, } = {}) { if (!(audioBytes instanceof Uint8Array) || audioBytes.byteLength === 0) { throw new Error('Audio payload is empty.'); @@ -30,7 +37,7 @@ async function transcribeChunkWithDeepgram({ utterances: 'true', diarize: 'false', }); - const response = await fetch( + return fetchJsonOrThrow( `${DEEPGRAM_BASE_URL.replace(/\/$/, '')}/v1/listen?${query.toString()}`, { method: 'POST', @@ -39,14 +46,11 @@ async function transcribeChunkWithDeepgram({ 'Content-Type': mimeType || 'application/octet-stream', }, body: audioBytes, + signal, }, + 'Deepgram request failed', + { maxResponseBytes: 10 * 1024 * 1024, timeoutMs: 60000 }, ); - - if (!response.ok) { - await throwResponseError(response, 'Deepgram request failed'); - } - - return response.json(); } const DEFAULT_STT_PROVIDER = 'openai'; @@ -92,25 +96,10 @@ const WEARABLE_SAFE_AUDIO_FORMAT = Object.freeze({ }); const MIN_STREAM_PCM_CHUNK_BYTES = 24000; const MAX_STREAM_PCM_CHUNK_BYTES = 48000; - -function withTimeout(promise, timeoutMs, label) { - const normalizedTimeout = Number(timeoutMs); - if (!Number.isFinite(normalizedTimeout) || normalizedTimeout <= 0) { - return promise; - } - let timer = null; - const timeoutPromise = new Promise((_, reject) => { - timer = setTimeout(() => { - reject(new Error(`${label} timed out after ${normalizedTimeout}ms.`)); - }, normalizedTimeout); - timer.unref?.(); - }); - return Promise.race([promise, timeoutPromise]).finally(() => { - if (timer) { - clearTimeout(timer); - } - }); -} +const DEFAULT_STT_TIMEOUT_MS = 60000; +const DEFAULT_TTS_TIMEOUT_MS = 30000; +const MAX_JSON_RESPONSE_BYTES = 40 * 1024 * 1024; +const MAX_AUDIO_RESPONSE_BYTES = 32 * 1024 * 1024; function sanitizeSpeechText(value) { const text = String(value || ''); @@ -212,46 +201,71 @@ function requireApiKey(settingLabel, candidates = []) { return apiKey; } -async function throwResponseError(response, prefix) { - const body = await response.text(); - throw new Error(`${prefix} (${response.status}): ${body || 'empty response'}`); -} - -async function fetchJsonOrThrow(url, init, errorPrefix) { - const response = await fetch(url, init); - if (!response.ok) { - await throwResponseError(response, errorPrefix); - } - return response.json(); -} - -async function fetchAudioOrThrow(url, init, errorPrefix, defaultMimeType = 'audio/mpeg') { - const response = await fetch(url, init); +function responseError(prefix, status, body) { + const error = new Error( + `${prefix} (${status}): ${String(body || 'empty response').slice(0, 2000)}`, + ); + error.status = status; + return error; +} + +async function fetchJsonOrThrow(url, init, errorPrefix, options = {}) { + const { response, text } = await fetchResponseText(url, { + ...init, + timeoutMs: options.timeoutMs || DEFAULT_STT_TIMEOUT_MS, + maxResponseBytes: options.maxResponseBytes || MAX_JSON_RESPONSE_BYTES, + serviceName: errorPrefix, + timeoutCode: 'VOICE_PROVIDER_TIMEOUT', + tooLargeCode: 'VOICE_PROVIDER_RESPONSE_TOO_LARGE', + }); + if (!response.ok) throw responseError(errorPrefix, response.status, text); + try { + return JSON.parse(text); + } catch (cause) { + throw new Error(`${errorPrefix}: provider returned malformed JSON.`, { cause }); + } +} + +async function fetchAudioOrThrow( + url, + init, + errorPrefix, + defaultMimeType = 'audio/mpeg', + options = {}, +) { + const { response, body } = await fetchResponseBuffer(url, { + ...init, + timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS, + maxResponseBytes: options.maxResponseBytes || MAX_AUDIO_RESPONSE_BYTES, + serviceName: errorPrefix, + timeoutCode: 'VOICE_PROVIDER_TIMEOUT', + tooLargeCode: 'VOICE_PROVIDER_RESPONSE_TOO_LARGE', + }); if (!response.ok) { - await throwResponseError(response, errorPrefix); + throw responseError(errorPrefix, response.status, body.toString('utf8')); } return { - audioBytes: Buffer.from(await response.arrayBuffer()), + audioBytes: body, mimeType: response.headers.get('content-type') || defaultMimeType, }; } -async function fetchAudioStreamOrThrow(url, init, errorPrefix, defaultMimeType = 'audio/mpeg', onChunk) { - const response = await fetch(url, init); - if (!response.ok) { - await throwResponseError(response, errorPrefix); - } - const mimeType = response.headers.get('content-type') || defaultMimeType; - const reader = response.body.getReader(); - const chunks = []; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - if (value?.length) { - chunks.push(Buffer.from(value)); - } - } - await onChunk({ audioBytes: Buffer.concat(chunks), mimeType }); +async function fetchAudioStreamOrThrow( + url, + init, + errorPrefix, + defaultMimeType, + onChunk, + options = {}, +) { + const audio = await fetchAudioOrThrow( + url, + init, + errorPrefix, + defaultMimeType, + options, + ); + await onChunk(audio); } function guessExtFromMimeType(mimeType) { @@ -312,11 +326,26 @@ function wrapPcmAsWav(audioBytes, format) { return Buffer.concat([header, data]); } -async function streamPcmAsWavChunks(readable, format, onChunk) { +async function emitPcmBufferAsWavChunks(audioBytes, format, onChunk, signal = null) { + const source = Buffer.isBuffer(audioBytes) ? audioBytes : Buffer.from(audioBytes || []); + for (let offset = 0; offset < source.length; offset += MAX_STREAM_PCM_CHUNK_BYTES) { + throwIfAborted(signal, 'Voice synthesis aborted.'); + const end = Math.min(source.length, offset + MAX_STREAM_PCM_CHUNK_BYTES); + const evenEnd = end - ((end - offset) % 2); + if (evenEnd <= offset) continue; + await onChunk({ + audioBytes: wrapPcmAsWav(source.subarray(offset, evenEnd), format), + mimeType: WEARABLE_SAFE_AUDIO_FORMAT.streamMimeType, + }); + } +} + +async function streamPcmAsWavChunks(readable, format, onChunk, options = {}) { const source = readable && typeof readable.getReader === 'function' ? readable : null; let pending = Buffer.alloc(0); + let totalBytes = 0; async function flushPending(force = false) { while (pending.length >= MIN_STREAM_PCM_CHUNK_BYTES || (force && pending.length > 0)) { @@ -327,6 +356,7 @@ async function streamPcmAsWavChunks(readable, format, onChunk) { if (evenLength <= 0) return; const pcmChunk = pending.subarray(0, evenLength); pending = pending.subarray(evenLength); + throwIfAborted(options.signal, 'Voice synthesis aborted.'); await onChunk({ audioBytes: wrapPcmAsWav(pcmChunk, format), mimeType: WEARABLE_SAFE_AUDIO_FORMAT.streamMimeType, @@ -340,6 +370,13 @@ async function streamPcmAsWavChunks(readable, format, onChunk) { const { done, value } = await reader.read(); if (done) break; if (value?.length) { + totalBytes += value.length; + if (totalBytes > MAX_AUDIO_RESPONSE_BYTES) { + await reader.cancel().catch(() => {}); + const error = new Error('Voice audio response exceeded its safety limit.'); + error.code = 'VOICE_PROVIDER_RESPONSE_TOO_LARGE'; + throw error; + } pending = Buffer.concat([pending, Buffer.from(value)]); await flushPending(false); } @@ -347,7 +384,14 @@ async function streamPcmAsWavChunks(readable, format, onChunk) { } else { for await (const chunk of readable) { if (chunk?.length) { - pending = Buffer.concat([pending, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]); + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + totalBytes += buffer.length; + if (totalBytes > MAX_AUDIO_RESPONSE_BYTES) { + const error = new Error('Voice audio response exceeded its safety limit.'); + error.code = 'VOICE_PROVIDER_RESPONSE_TOO_LARGE'; + throw error; + } + pending = Buffer.concat([pending, buffer]); await flushPending(false); } } @@ -363,19 +407,28 @@ async function transcribeWithOpenAi(filePath, model, options = {}) { if (!client) { throw new Error('OpenAI STT is selected but OPENAI_API_KEY is not configured.'); } - const transcription = await client.audio.transcriptions.create({ - file: fs.createReadStream(filePath), - model, - }); - return String(transcription?.text || '').trim(); + const file = fs.createReadStream(filePath); + const abortFile = () => file.destroy(createAbortError(options.signal)); + options.signal?.addEventListener('abort', abortFile, { once: true }); + try { + const transcription = await client.audio.transcriptions.create({ + file, + model, + }, { signal: options.signal }); + return String(transcription?.text || '').trim(); + } finally { + options.signal?.removeEventListener('abort', abortFile); + file.destroy(); + } } -async function transcribeWithDeepgram(filePath, mimeType) { - const audioBytes = await fs.promises.readFile(filePath); +async function transcribeWithDeepgram(filePath, mimeType, options = {}) { + const audioBytes = await fs.promises.readFile(filePath, { signal: options.signal }); const payload = await transcribeChunkWithDeepgram({ audioBytes, mimeType: mimeType || 'audio/mpeg', detectLanguage: 'multi', + signal: options.signal, }); const transcript = payload?.results?.channels?.[0]?.alternatives?.[0]?.transcript; @@ -387,13 +440,14 @@ async function transcribeWithGemini(filePath, model, mimeType, options = {}) { (typeof options.apiKey === 'string' ? options.apiKey.trim() : '') || requireApiKey('Gemini STT', ['GOOGLE_AI_KEY', 'GEMINI_API_KEY']); - const audioBytes = await fs.promises.readFile(filePath); + const audioBytes = await fs.promises.readFile(filePath, { signal: options.signal }); const payload = await fetchJsonOrThrow( - `${GEMINI_API_BASE_URL}/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`, + `${GEMINI_API_BASE_URL}/${encodeURIComponent(model)}:generateContent`, { method: 'POST', headers: { 'Content-Type': 'application/json', + 'x-goog-api-key': apiKey, }, body: JSON.stringify({ contents: [ @@ -415,8 +469,10 @@ async function transcribeWithGemini(filePath, model, mimeType, options = {}) { temperature: 0, }, }), + signal: options.signal, }, 'Gemini STT request failed', + { maxResponseBytes: MAX_JSON_RESPONSE_BYTES, timeoutMs: DEFAULT_STT_TIMEOUT_MS }, ); const parts = payload?.candidates?.[0]?.content?.parts; const transcript = Array.isArray(parts) @@ -428,16 +484,21 @@ async function transcribeWithGemini(filePath, model, mimeType, options = {}) { async function transcribeVoiceInput(filePath, options = {}) { const provider = normalizeSttProvider(options.provider); const model = resolveSttModel(provider, options.model); - let request = null; - - if (provider === 'openai') { - request = transcribeWithOpenAi(filePath, model, options); - } else if (provider === 'deepgram') { - request = transcribeWithDeepgram(filePath, options.mimeType); - } else { - request = transcribeWithGemini(filePath, model, options.mimeType, options); - } - return withTimeout(request, options.timeoutMs, `${provider} STT`); + return runWithAbortTimeout(async (signal) => { + const requestOptions = { ...options, signal }; + if (provider === 'openai') { + return transcribeWithOpenAi(filePath, model, requestOptions); + } + if (provider === 'deepgram') { + return transcribeWithDeepgram(filePath, options.mimeType, requestOptions); + } + return transcribeWithGemini(filePath, model, options.mimeType, requestOptions); + }, { + signal: options.signal, + timeoutMs: options.timeoutMs || DEFAULT_STT_TIMEOUT_MS, + timeoutCode: 'VOICE_STT_TIMEOUT', + label: `${provider} STT`, + }); } async function synthesizeWithOpenAi(text, model, voice, options = {}) { @@ -453,6 +514,9 @@ async function synthesizeWithOpenAi(text, model, voice, options = {}) { model, voice, responseFormat: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.responseFormat : 'mp3', + signal: options.signal, + timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS, + maxResponseBytes: options.maxResponseBytes || MAX_AUDIO_RESPONSE_BYTES, }); return { audioBytes, @@ -474,7 +538,7 @@ async function streamWithOpenAi(text, model, voice, options = {}, onChunk) { voice: String(voice || 'alloy').trim() || 'alloy', input: text, response_format: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.streamResponseFormat : 'mp3', - }); + }, { signal: options.signal }); if (useWearableSafeAudio) { await streamPcmAsWavChunks( response.body, @@ -484,12 +548,22 @@ async function streamWithOpenAi(text, model, voice, options = {}, onChunk) { channels: WEARABLE_SAFE_AUDIO_FORMAT.pcmChannels, }, onChunk, + { signal: options.signal }, ); return; } const chunks = []; + let totalBytes = 0; for await (const chunk of response.body) { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + throwIfAborted(options.signal, 'OpenAI TTS stream aborted.'); + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + totalBytes += buffer.length; + if (totalBytes > MAX_AUDIO_RESPONSE_BYTES) { + const error = new Error('OpenAI speech response exceeded its safety limit.'); + error.code = 'VOICE_PROVIDER_RESPONSE_TOO_LARGE'; + throw error; + } + chunks.push(buffer); } const audioBytes = Buffer.concat(chunks); await onChunk({ @@ -518,9 +592,11 @@ async function synthesizeWithDeepgram(text, model, options = {}) { 'Content-Type': 'application/json', }, body: JSON.stringify({ text }), + signal: options.signal, }, 'Deepgram TTS request failed', useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg', + { timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS }, ); } @@ -534,7 +610,7 @@ async function streamWithDeepgram(text, model, options = {}, onChunk) { searchParams.set('encoding', WEARABLE_SAFE_AUDIO_FORMAT.deepgramEncoding); searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.deepgramStreamContainer); searchParams.set('sample_rate', String(WEARABLE_SAFE_AUDIO_FORMAT.pcmSampleRate)); - const response = await fetch( + const audio = await fetchAudioOrThrow( `https://api.deepgram.com/v1/speak?${searchParams.toString()}`, { method: 'POST', @@ -543,19 +619,21 @@ async function streamWithDeepgram(text, model, options = {}, onChunk) { 'Content-Type': 'application/json', }, body: JSON.stringify({ text }), + signal: options.signal, }, + 'Deepgram TTS stream failed', + 'audio/pcm', + { timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS }, ); - if (!response.ok) { - await throwResponseError(response, 'Deepgram TTS stream failed'); - } - await streamPcmAsWavChunks( - response.body, + await emitPcmBufferAsWavChunks( + audio.audioBytes, { bitsPerSample: WEARABLE_SAFE_AUDIO_FORMAT.pcmBitsPerSample, sampleRate: WEARABLE_SAFE_AUDIO_FORMAT.pcmSampleRate, channels: WEARABLE_SAFE_AUDIO_FORMAT.pcmChannels, }, onChunk, + options.signal, ); return; } @@ -568,10 +646,12 @@ async function streamWithDeepgram(text, model, options = {}, onChunk) { 'Content-Type': 'application/json', }, body: JSON.stringify({ text }), + signal: options.signal, }, 'Deepgram TTS stream failed', useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg', onChunk, + { timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS }, ); } @@ -581,11 +661,12 @@ async function synthesizeWithGemini(text, model, voice, options = {}) { requireApiKey('Gemini TTS', ['GOOGLE_AI_KEY', 'GEMINI_API_KEY']); const payload = await fetchJsonOrThrow( - `${GEMINI_API_BASE_URL}/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`, + `${GEMINI_API_BASE_URL}/${encodeURIComponent(model)}:generateContent`, { method: 'POST', headers: { 'Content-Type': 'application/json', + 'x-goog-api-key': apiKey, }, body: JSON.stringify({ contents: [ @@ -605,8 +686,10 @@ async function synthesizeWithGemini(text, model, voice, options = {}) { temperature: 0.6, }, }), + signal: options.signal, }, 'Gemini TTS request failed', + { maxResponseBytes: MAX_JSON_RESPONSE_BYTES, timeoutMs: DEFAULT_TTS_TIMEOUT_MS }, ); const parts = payload?.candidates?.[0]?.content?.parts; const audioPart = Array.isArray(parts) @@ -674,11 +757,14 @@ async function streamWithGemini(text, model, voice, options = {}, onChunk) { (typeof options.apiKey === 'string' ? options.apiKey.trim() : '') || requireApiKey('Gemini TTS', ['GOOGLE_AI_KEY', 'GEMINI_API_KEY']); - const response = await fetch( - `${GEMINI_API_BASE_URL}/${encodeURIComponent(model)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(apiKey)}`, + const { response, text: eventStream } = await fetchResponseText( + `${GEMINI_API_BASE_URL}/${encodeURIComponent(model)}:streamGenerateContent?alt=sse`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'x-goog-api-key': apiKey, + }, body: JSON.stringify({ contents: [{ parts: [{ text }] }], generationConfig: { @@ -693,54 +779,40 @@ async function streamWithGemini(text, model, voice, options = {}, onChunk) { temperature: 0.6, }, }), + signal: options.signal, + timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS, + maxResponseBytes: MAX_JSON_RESPONSE_BYTES, + serviceName: 'Gemini TTS stream', + timeoutCode: 'VOICE_PROVIDER_TIMEOUT', + tooLargeCode: 'VOICE_PROVIDER_RESPONSE_TOO_LARGE', }, ); - if (!response.ok) { - await throwResponseError(response, 'Gemini TTS stream request failed'); - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ''; - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += decoder.decode(value, { stream: true }); - - // SSE lines: each event is "data: {...}\n\n" - const lines = buffer.split('\n'); - buffer = lines.pop(); // keep incomplete line - - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed.startsWith('data:')) continue; - const jsonStr = trimmed.slice(5).trim(); - if (!jsonStr || jsonStr === '[DONE]') continue; - let parsed; - try { - parsed = JSON.parse(jsonStr); - } catch { - continue; - } - const chunk = extractGeminiAudioChunk(parsed); - if (chunk) await onChunk(chunk); + throw responseError('Gemini TTS stream request failed', response.status, eventStream); + } + + let totalAudioBytes = 0; + for (const line of eventStream.split('\n')) { + throwIfAborted(options.signal, 'Gemini TTS stream aborted.'); + const trimmed = line.trim(); + if (!trimmed.startsWith('data:')) continue; + const jsonStr = trimmed.slice(5).trim(); + if (!jsonStr || jsonStr === '[DONE]') continue; + let parsed; + try { + parsed = JSON.parse(jsonStr); + } catch { + continue; } - } - - // Flush any remaining buffered data. - if (buffer.trim().startsWith('data:')) { - const jsonStr = buffer.trim().slice(5).trim(); - if (jsonStr && jsonStr !== '[DONE]') { - try { - const parsed = JSON.parse(jsonStr); - const chunk = extractGeminiAudioChunk(parsed); - if (chunk) await onChunk(chunk); - } catch { - // Ignore incomplete trailing chunk. - } + const chunk = extractGeminiAudioChunk(parsed); + if (!chunk) continue; + totalAudioBytes += chunk.audioBytes.length; + if (totalAudioBytes > MAX_AUDIO_RESPONSE_BYTES) { + const error = new Error('Gemini speech response exceeded its safety limit.'); + error.code = 'VOICE_PROVIDER_RESPONSE_TOO_LARGE'; + throw error; } + await onChunk(chunk); } } @@ -751,16 +823,21 @@ async function synthesizeVoiceReply(text, options = {}) { } const { provider, model, voice } = normalizeVoiceSynthesisOptions(options); - let request = null; - - if (provider === 'openai') { - request = synthesizeWithOpenAi(content, model, voice, options); - } else if (provider === 'deepgram') { - request = synthesizeWithDeepgram(content, model, options); - } else { - request = synthesizeWithGemini(content, model, voice, options); - } - return withTimeout(request, options.timeoutMs, `${provider} TTS`); + return runWithAbortTimeout(async (signal) => { + const requestOptions = { ...options, signal }; + if (provider === 'openai') { + return synthesizeWithOpenAi(content, model, voice, requestOptions); + } + if (provider === 'deepgram') { + return synthesizeWithDeepgram(content, model, requestOptions); + } + return synthesizeWithGemini(content, model, voice, requestOptions); + }, { + signal: options.signal, + timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS, + timeoutCode: 'VOICE_TTS_TIMEOUT', + label: `${provider} TTS`, + }); } // Minimum characters before flushing a sentence chunk to TTS to avoid tiny requests. @@ -834,16 +911,21 @@ async function synthesizeVoiceReplyStream(text, options = {}, onChunk) { const chunks = splitIntoSentenceChunks(content); for (const chunk of chunks) { - const run = (async () => { + await runWithAbortTimeout(async (signal) => { + const requestOptions = { ...options, signal }; if (provider === 'openai') { - await streamWithOpenAi(chunk, model, voice, options, onChunk); + await streamWithOpenAi(chunk, model, voice, requestOptions, onChunk); } else if (provider === 'deepgram') { - await streamWithDeepgram(chunk, model, options, onChunk); + await streamWithDeepgram(chunk, model, requestOptions, onChunk); } else { - await streamWithGemini(chunk, model, voice, options, onChunk); + await streamWithGemini(chunk, model, voice, requestOptions, onChunk); } - })(); - await withTimeout(run, options.timeoutMs, `${provider} TTS stream`); + }, { + signal: options.signal, + timeoutMs: options.timeoutMs || DEFAULT_TTS_TIMEOUT_MS, + timeoutCode: 'VOICE_TTS_TIMEOUT', + label: `${provider} TTS stream`, + }); } } diff --git a/server/services/voice/runtimeManager.js b/server/services/voice/runtimeManager.js index aa76b8f5..3b587e8c 100644 --- a/server/services/voice/runtimeManager.js +++ b/server/services/voice/runtimeManager.js @@ -16,6 +16,15 @@ const { sanitizeSpeechText, } = require('./providers'); const { VoiceAgentBridge } = require('./agentBridge'); +const { waitForBoundedResult } = require('../network/http'); +const { createAbortError, throwIfAborted } = require('../../utils/abort'); + +function voiceRuntimeStoppedError() { + const error = new Error('Voice runtime is shutting down.'); + error.name = 'AbortError'; + error.code = 'VOICE_RUNTIME_SHUTDOWN'; + return error; +} class VoiceRuntimeManager { constructor({ io, agentEngine, memoryManager }) { @@ -23,6 +32,8 @@ class VoiceRuntimeManager { this.agentEngine = agentEngine; this.memoryManager = memoryManager; this.sessions = new Map(); + this.shuttingDown = false; + this.shutdownPromise = null; this.agentBridge = new VoiceAgentBridge({ agentEngine, memoryManager, @@ -34,6 +45,10 @@ class VoiceRuntimeManager { return this.sessions.get(String(sessionId || '').trim()) || null; } + #assertRunning() { + if (this.shuttingDown) throw voiceRuntimeStoppedError(); + } + async openSession({ userId, agentId = null, @@ -42,6 +57,7 @@ class VoiceRuntimeManager { sink, outputMode = 'audio_and_text', } = {}) { + this.#assertRunning(); if (!sink) { throw new Error('A voice session sink is required.'); } @@ -57,6 +73,14 @@ class VoiceRuntimeManager { } const adapter = this.#createAdapter(voiceSettings.liveProvider); await adapter.open(); + if (this.shuttingDown) { + await adapter.close?.(resolvedSessionId).catch(() => {}); + throw voiceRuntimeStoppedError(); + } + if (this.sessions.has(resolvedSessionId)) { + await adapter.close?.(resolvedSessionId).catch(() => {}); + throw new Error('Voice session ID collision: a session with this ID already exists.'); + } const session = new VoiceLiveSession({ id: resolvedSessionId, @@ -74,8 +98,15 @@ class VoiceRuntimeManager { session.adapter = adapter; this.sessions.set(resolvedSessionId, session); - await session.publishReady(); - return session; + try { + await session.publishReady(); + return session; + } catch (error) { + this.sessions.delete(resolvedSessionId); + await session.close('startup_failed').catch(() => {}); + await adapter.close?.(resolvedSessionId).catch(() => {}); + throw error; + } } async openFlutterSession({ userId, agentId = null, socket, sessionId = null } = {}) { @@ -162,12 +193,12 @@ class VoiceRuntimeManager { if (userId != null && session.userId != null && String(session.userId) !== String(userId)) { throw new Error('Voice session access denied.'); } - if (reason === 'socket_disconnected') { - await this.abortActiveRun(session.id, 'voice_disconnect'); - } + await this.abortActiveRun(session.id, reason === 'socket_disconnected' + ? 'voice_disconnect' + : 'voice_session_closed'); this.sessions.delete(session.id); - await session.adapter?.close?.(session.id); await session.close(reason); + await session.adapter?.close?.(session.id); } async beginInput(sessionId, options = {}, userId = null) { @@ -255,7 +286,13 @@ class VoiceRuntimeManager { }; } - async deliverDeferredVoiceFollowUp(session, followUpPlan, replyText, runId = null) { + async deliverDeferredVoiceFollowUp( + session, + followUpPlan, + replyText, + runId = null, + options = {}, + ) { if (!session || session.closed || !followUpPlan || session.deferFollowUpRequested !== true) { return { sent: false, skipped: true }; } @@ -298,9 +335,11 @@ class VoiceRuntimeManager { agentId: session.agentId, runId, persistConversation: true, + signal: options.signal, }, ); } catch (err) { + if (options.signal?.aborted) throw createAbortError(options.signal); console.error('Failed to send deferred voice follow-up:', err); return { sent: false, @@ -334,6 +373,7 @@ class VoiceRuntimeManager { sink, metadata = null, } = {}) { + this.#assertRunning(); const sessionId = `telnyx:${userId}:${callId}`; let session = this.getSession(sessionId); if (!session) { @@ -358,10 +398,18 @@ class VoiceRuntimeManager { }); session.adapter = this.#createAdapter(voiceSettings.liveProvider); await session.adapter.open(); + if (this.shuttingDown) { + await session.close('server_shutdown').catch(() => {}); + await session.adapter.close?.(sessionId).catch(() => {}); + throw voiceRuntimeStoppedError(); + } this.sessions.set(sessionId, session); } await session.interruptOutput(); + await this.abortActiveRun(session.id, 'voice_interrupt'); + session.resetTurnState(); + const turnSignal = session.signal; await session.setState('thinking'); try { @@ -370,7 +418,9 @@ class VoiceRuntimeManager { }); return result; } finally { - await session.setState('idle'); + if (!session.closed && session.signal === turnSignal && !turnSignal.aborted) { + await session.setState('idle'); + } } } @@ -378,7 +428,29 @@ class VoiceRuntimeManager { const session = this.getSession(sessionId); const runId = session?.currentRunId; if (!runId || !this.agentEngine) return; - this.agentEngine.abort(runId, reason); + this.agentEngine.abort(runId, { userId: session.userId, reason }); + } + + shutdown() { + if (this.shutdownPromise) return this.shutdownPromise; + this.shuttingDown = true; + const sessionIds = Array.from(this.sessions.keys()); + const closing = Promise.allSettled( + sessionIds.map((sessionId) => this.closeSession(sessionId, 'server_shutdown')), + ); + this.shutdownPromise = waitForBoundedResult(closing, { + serviceName: 'Voice runtime shutdown', + timeoutMs: 10000, + }).then((results) => ({ + state: 'stopped', + timedOut: false, + failedSessionCount: results.filter((result) => result.status === 'rejected').length, + })).catch((error) => ({ + state: 'timeout', + timedOut: error?.code === 'HTTP_TIMEOUT', + error: error?.message || String(error), + })); + return this.shutdownPromise; } #createAdapter(provider) { @@ -389,6 +461,7 @@ class VoiceRuntimeManager { } #requireSession(sessionId, userId = null) { + this.#assertRunning(); const session = this.getSession(sessionId); if (!session) { throw new Error('Voice session was not found.'); @@ -466,6 +539,8 @@ class VoiceRuntimeManager { } async #deliverFlutterAssistantOutput(socket, sessionId, session, content, options = {}) { + const turnSignal = session.signal; + throwIfAborted(turnSignal, 'Voice output was interrupted.'); const kind = String(options.kind || 'final').trim() || 'final'; socket.emit('voice:assistant_text', { sessionId, @@ -486,7 +561,7 @@ class VoiceRuntimeManager { let index = 0; let streamError = null; - const ttsAttempts = this.#buildTtsAttemptOrder(session, voiceOptions); + const ttsAttempts = this.#buildTtsAttemptOrder(session, voiceOptions, turnSignal); if (spokenContent) { try { for (const attempt of ttsAttempts) { @@ -498,7 +573,12 @@ class VoiceRuntimeManager { spokenContent, attempt, async ({ audioBytes, mimeType }) => { - if (session.closed || session.interrupted) return; + if ( + session.closed + || session.interrupted + || turnSignal.aborted + || session.signal !== turnSignal + ) return; socket.emit('voice:audio_chunk', { sessionId, kind, @@ -516,6 +596,7 @@ class VoiceRuntimeManager { streamError = null; break; } catch (error) { + if (turnSignal.aborted || session.signal !== turnSignal) return; streamError = String(error?.message || error || 'Voice playback failed.'); console.warn(`[VoiceRuntime] ${attempt.provider} TTS failed for flutter session ${sessionId}: ${streamError}`); if (attemptChunks > 0) { @@ -524,13 +605,16 @@ class VoiceRuntimeManager { } } } catch (error) { + if (turnSignal.aborted || session.signal !== turnSignal) return; streamError = String(error?.message || error || 'Voice playback failed.'); } } - if (!streamError && !session.closed && !session.interrupted) { + if (!streamError && !session.closed && !session.interrupted + && !turnSignal.aborted && session.signal === turnSignal) { socket.emit('voice:audio_done', { sessionId, kind, totalChunks: index }); - } else if (kind === 'final' && !session.closed && !session.interrupted) { + } else if (kind === 'final' && !session.closed && !session.interrupted + && !turnSignal.aborted && session.signal === turnSignal) { socket.emit('voice:error', { sessionId, error: streamError, @@ -540,7 +624,8 @@ class VoiceRuntimeManager { await session.setState('degraded', { kind, phase: 'tts' }); } - if (kind === 'final' && !streamError) { + if (kind === 'final' && !streamError + && !turnSignal.aborted && session.signal === turnSignal) { await session.setState('idle'); } } @@ -550,6 +635,8 @@ class VoiceRuntimeManager { if (!session) { return; } + const turnSignal = session.signal; + throwIfAborted(turnSignal, 'Voice output was interrupted.'); const kind = String(options.kind || 'final').trim() || 'final'; ws.send(JSON.stringify({ type: 'voice:assistant_text', @@ -572,7 +659,7 @@ class VoiceRuntimeManager { const spokenContent = sanitizeSpeechText(content); let index = 0; let streamError = null; - const ttsAttempts = this.#buildTtsAttemptOrder(session, voiceOptions); + const ttsAttempts = this.#buildTtsAttemptOrder(session, voiceOptions, turnSignal); if (spokenContent) { try { @@ -585,7 +672,12 @@ class VoiceRuntimeManager { spokenContent, attempt, async ({ audioBytes, mimeType }) => { - if (session.closed || session.interrupted) return; + if ( + session.closed + || session.interrupted + || turnSignal.aborted + || session.signal !== turnSignal + ) return; ws.send(JSON.stringify({ type: 'voice:audio_chunk', sessionId, @@ -603,6 +695,7 @@ class VoiceRuntimeManager { } break; } catch (error) { + if (turnSignal.aborted || session.signal !== turnSignal) return; streamError = String(error?.message || error || 'Voice playback failed.'); console.warn(`[VoiceRuntime] ${attempt.provider} TTS failed for wearable session ${sessionId}: ${streamError}`); if (attemptChunks > 0) { @@ -611,18 +704,21 @@ class VoiceRuntimeManager { } } } catch (error) { + if (turnSignal.aborted || session.signal !== turnSignal) return; streamError = String(error?.message || error || 'Voice playback failed.'); } } - if (!streamError && !session.closed && !session.interrupted) { + if (!streamError && !session.closed && !session.interrupted + && !turnSignal.aborted && session.signal === turnSignal) { ws.send(JSON.stringify({ type: 'voice:audio_done', sessionId, kind, totalChunks: index, })); - } else if (kind === 'final' && !session.closed && !session.interrupted) { + } else if (kind === 'final' && !session.closed && !session.interrupted + && !turnSignal.aborted && session.signal === turnSignal) { ws.send(JSON.stringify({ type: 'voice:error', sessionId, @@ -633,12 +729,13 @@ class VoiceRuntimeManager { await session.setState('degraded', { kind, phase: 'tts' }); } - if (kind === 'final' && !streamError) { + if (kind === 'final' && !streamError + && !turnSignal.aborted && session.signal === turnSignal) { await session.setState('idle'); } } - #buildTtsAttemptOrder(session, voiceOptions) { + #buildTtsAttemptOrder(session, voiceOptions, signal = null) { const attempts = []; const providers = [ voiceOptions.provider, @@ -663,6 +760,7 @@ class VoiceRuntimeManager { apiKey: runtime.apiKey, baseUrl: runtime.baseUrl, timeoutMs: 20000, + signal, }); } return attempts; diff --git a/server/services/voice/turnRunner.js b/server/services/voice/turnRunner.js index 2cccd56c..b56ff4c8 100644 --- a/server/services/voice/turnRunner.js +++ b/server/services/voice/turnRunner.js @@ -13,6 +13,7 @@ const { VOICE_HISTORY_WINDOW, buildDirectVoiceRunOptions, } = require('./runtime'); +const { createAbortError, throwIfAborted } = require('../../utils/abort'); async function runVoiceTranscriptTurn({ userId, @@ -30,10 +31,12 @@ async function runVoiceTranscriptTurn({ allowInterimUpdates = false, voiceSessionId = null, runId = null, + signal = null, }) { if (!agentEngine || !memoryManager) { throw new Error('Voice turn service is not initialized.'); } + throwIfAborted(signal, 'Voice turn aborted before startup.'); const transcriptText = String(transcript || '').trim(); if (!transcriptText) { @@ -83,6 +86,7 @@ async function runVoiceTranscriptTurn({ priorMessages, priorSummary, voiceSessionId, + signal, context: { rawUserMessage: storedUserContent, additionalContext: directVoiceContext, @@ -161,6 +165,7 @@ async function runVoiceTranscriptTurn({ apiKey: runtime.apiKey, baseUrl: runtime.baseUrl, timeoutMs: 12000, + signal, }); providerUsed = normalized.provider; modelUsed = normalized.model; @@ -168,6 +173,7 @@ async function runVoiceTranscriptTurn({ ttsError = null; break; } catch (error) { + if (signal?.aborted) throw createAbortError(signal); lastTtsError = error; } } diff --git a/server/services/wearable/firmware_manifest.js b/server/services/wearable/firmware_manifest.js index bfd2750c..65b58eab 100644 --- a/server/services/wearable/firmware_manifest.js +++ b/server/services/wearable/firmware_manifest.js @@ -1,8 +1,14 @@ 'use strict'; +const { createAbortError } = require('../../utils/abort'); +const { fetchResponseText } = require('../network/http'); + const DEFAULT_GITHUB_REPOSITORY = 'NeoLabs-Systems/NeoAgent'; const DEFAULT_ASSET_NAME = 'neoagent-wearable-firmware.bin'; const MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000; +const FIRMWARE_HTTP_TIMEOUT_MS = 15000; +const RELEASES_MAX_RESPONSE_BYTES = 2 * 1024 * 1024; +const CHECKSUM_MAX_RESPONSE_BYTES = 64 * 1024; const manifestCache = new Map(); function trimString(value, maxLength = 512) { @@ -118,36 +124,53 @@ function selectChecksumAsset(release, assetName) { }) || null; } -async function fetchGithubJson(fetchImpl, url, token) { - const response = await fetchImpl(url, { +async function fetchGithubJson(fetchImpl, url, token, signal) { + const { response, text } = await fetchResponseText(url, { + fetchImpl, headers: { Accept: 'application/vnd.github+json', ...(token ? { Authorization: `Bearer ${token}` } : {}), }, + signal, + timeoutMs: FIRMWARE_HTTP_TIMEOUT_MS, + maxResponseBytes: RELEASES_MAX_RESPONSE_BYTES, + serviceName: 'GitHub firmware release lookup', + timeoutCode: 'FIRMWARE_HTTP_TIMEOUT', + tooLargeCode: 'FIRMWARE_RESPONSE_TOO_LARGE', }); if (!response.ok) { - const body = await response.text().catch(() => ''); const error = new Error(`GitHub API request failed with ${response.status}`); error.status = response.status; - error.body = body; + error.body = text.slice(0, 2000); throw error; } - return response.json(); + try { + return JSON.parse(text); + } catch (cause) { + throw new Error('GitHub firmware release lookup returned malformed JSON.', { cause }); + } } -async function fetchText(fetchImpl, url, token) { - const response = await fetchImpl(url, { +async function fetchText(fetchImpl, url, token, signal) { + const { response, text } = await fetchResponseText(url, { + fetchImpl, headers: { Accept: 'text/plain, application/octet-stream;q=0.9, */*;q=0.1', ...(token ? { Authorization: `Bearer ${token}` } : {}), }, + signal, + timeoutMs: FIRMWARE_HTTP_TIMEOUT_MS, + maxResponseBytes: CHECKSUM_MAX_RESPONSE_BYTES, + serviceName: 'Firmware checksum download', + timeoutCode: 'FIRMWARE_HTTP_TIMEOUT', + tooLargeCode: 'FIRMWARE_RESPONSE_TOO_LARGE', }); if (!response.ok) { const error = new Error(`Asset request failed with ${response.status}`); error.status = response.status; throw error; } - return response.text(); + return text; } function parseChecksumBody(body, assetName) { @@ -168,9 +191,14 @@ function parseChecksumBody(body, assetName) { return null; } -async function fetchGithubRelease(fetchImpl, repository, channel, token, assetName = null) { +async function fetchGithubRelease(fetchImpl, repository, channel, token, assetName = null, signal = null) { const normalizedChannel = normalizeChannel(channel); - const releases = await fetchGithubJson(fetchImpl, `https://api.github.com/repos/${repository}/releases?per_page=100`, token); + const releases = await fetchGithubJson( + fetchImpl, + `https://api.github.com/repos/${repository}/releases?per_page=100`, + token, + signal, + ); const release = selectGithubRelease(releases, normalizedChannel, assetName); if (!release) { const error = new Error(`No ${normalizedChannel} firmware release found for ${repository}`); @@ -212,6 +240,7 @@ async function resolveFirmwareManifest({ repositoryOverride, assetNameOverride, fetchImpl = fetch, + signal = null, } = {}) { const normalizedChannel = normalizeChannel(channel); const repository = parseRepositorySlug(repositoryOverride) || getGithubRepository(); @@ -264,7 +293,14 @@ async function resolveFirmwareManifest({ try { const token = getGithubToken(); - const release = await fetchGithubRelease(fetchImpl, repository, normalizedChannel, token, assetName); + const release = await fetchGithubRelease( + fetchImpl, + repository, + normalizedChannel, + token, + assetName, + signal, + ); const asset = selectReleaseAsset(release, assetName); if (!asset || !asset.browser_download_url) { return { @@ -293,10 +329,11 @@ async function resolveFirmwareManifest({ if (checksumAsset?.browser_download_url) { try { checksum = parseChecksumBody( - await fetchText(fetchImpl, checksumAsset.browser_download_url, token), + await fetchText(fetchImpl, checksumAsset.browser_download_url, token, signal), asset.name, ); - } catch { + } catch (error) { + if (signal?.aborted) throw createAbortError(signal); checksum = null; } } @@ -322,6 +359,7 @@ async function resolveFirmwareManifest({ setCachedManifest(cacheId, manifest); return manifest; } catch (error) { + if (signal?.aborted) throw createAbortError(signal); return { configured: false, source: 'github', diff --git a/server/services/wearable/service.js b/server/services/wearable/service.js index d5e60d80..522bceca 100644 --- a/server/services/wearable/service.js +++ b/server/services/wearable/service.js @@ -136,6 +136,7 @@ class WearableService { channel, repositoryOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_GITHUB_REPOSITORY, 256), assetNameOverride: toTrimmedString(process.env.NEOAGENT_WEARABLE_FIRMWARE_ASSET_NAME, 128), + signal: req?.signal, }); return { ...manifest, diff --git a/server/utils/abort.js b/server/utils/abort.js new file mode 100644 index 00000000..ba7fc5b6 --- /dev/null +++ b/server/utils/abort.js @@ -0,0 +1,96 @@ +'use strict'; + +function createAbortError(signalOrReason = null, fallback = 'Operation aborted.') { + const reason = signalOrReason && typeof signalOrReason === 'object' + && 'aborted' in signalOrReason + ? signalOrReason.reason + : signalOrReason; + if (reason instanceof Error) return reason; + const error = new Error( + String(reason || fallback), + ); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + +function isAbortError(error, signal = null) { + return signal?.aborted === true + || error?.name === 'AbortError' + || error?.code === 'ABORT_ERR'; +} + +function throwIfAborted(signal, fallback = 'Operation aborted.') { + if (signal?.aborted) throw createAbortError(signal, fallback); +} + +function createLinkedAbortController(signals = []) { + const controller = new AbortController(); + const listeners = new Map(); + for (const signal of new Set(signals.filter(Boolean))) { + if (typeof signal.addEventListener !== 'function') continue; + const forwardAbort = () => { + if (!controller.signal.aborted) controller.abort(signal.reason); + }; + listeners.set(signal, forwardAbort); + if (signal.aborted) { + forwardAbort(); + break; + } + signal.addEventListener('abort', forwardAbort, { once: true }); + } + return { + controller, + signal: controller.signal, + cleanup() { + for (const [signal, forwardAbort] of listeners) { + signal.removeEventListener('abort', forwardAbort); + } + listeners.clear(); + }, + }; +} + +async function runWithAbortTimeout(factory, options = {}) { + const timeoutMs = Number(options.timeoutMs); + const timeoutController = new AbortController(); + const linked = createLinkedAbortController([ + options.signal, + timeoutController.signal, + ]); + throwIfAborted(linked.signal, `${options.label || 'Operation'} aborted.`); + let timer = null; + let abortListener = null; + if (Number.isFinite(timeoutMs) && timeoutMs > 0) { + timer = setTimeout(() => { + const error = new Error( + `${options.label || 'Operation'} timed out after ${Math.floor(timeoutMs)}ms.`, + ); + error.code = options.timeoutCode || 'OPERATION_TIMEOUT'; + timeoutController.abort(error); + }, timeoutMs); + } + const aborted = new Promise((_, reject) => { + abortListener = () => reject(createAbortError(linked.signal)); + linked.signal.addEventListener('abort', abortListener, { once: true }); + }); + + try { + return await Promise.race([ + Promise.resolve().then(() => factory(linked.signal)), + aborted, + ]); + } finally { + if (timer) clearTimeout(timer); + if (abortListener) linked.signal.removeEventListener('abort', abortListener); + linked.cleanup(); + } +} + +module.exports = { + createAbortError, + createLinkedAbortController, + isAbortError, + runWithAbortTimeout, + throwIfAborted, +}; diff --git a/server/utils/cloud-security.js b/server/utils/cloud-security.js index 8892d7b1..b3c4643f 100644 --- a/server/utils/cloud-security.js +++ b/server/utils/cloud-security.js @@ -1,5 +1,28 @@ 'use strict'; +const dns = require('node:dns'); +const net = require('node:net'); + +const ALLOWED_SCHEMES = new Set(['http', 'https']); +const BLOCKED_ANDROID_INTENT_SCHEMES = new Set([ + 'about', + 'chrome', + 'chrome-extension', + 'content', + 'data', + 'file', + 'javascript', + 'vbscript', +]); + +function urlAbortError(signal) { + if (signal?.reason instanceof Error) return signal.reason; + const error = new Error('URL validation was aborted.'); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + return error; +} + // URL schemes that must never be navigated to in the cloud browser or Android. const BLOCKED_SCHEMES = new Set([ 'javascript', @@ -56,8 +79,26 @@ function isPrivateHost(hostname) { // IPv6 loopback, link-local and unique-local if (h === '::1' || h === '::') return true; - if (h.startsWith('fe80:')) return true; + if (/^fe[89ab][0-9a-f]:/.test(h)) return true; // fe80::/10 link-local + if (/^fe[c-f][0-9a-f]:/.test(h)) return true; // fec0::/10 deprecated site-local if (/^f[cd][0-9a-f]*:/.test(h)) return true; // fc00::/7 unique-local + if (h.startsWith('ff')) return true; // IPv6 multicast + if (h === '100::' || h.startsWith('100::')) return true; // discard-only prefix + if (h === '2001:db8::' || h.startsWith('2001:db8:')) return true; // documentation + + if (net.isIPv4(h)) { + const [a, b, c] = h.split('.').map(Number); + if (a === 0 || a === 10 || a === 127 || a >= 224) return true; + if (a === 100 && b >= 64 && b <= 127) return true; + if (a === 169 && b === 254) return true; + if (a === 172 && b >= 16 && b <= 31) return true; + if (a === 192 && b === 168) return true; + if (a === 192 && b === 0 && c === 0) return true; + if (a === 192 && b === 0 && c === 2) return true; + if (a === 198 && (b === 18 || b === 19)) return true; + if (a === 198 && b === 51 && c === 100) return true; + if (a === 203 && b === 0 && c === 113) return true; + } for (const pattern of PRIVATE_IPV4) { if (pattern.test(h)) return true; @@ -90,7 +131,7 @@ function validateCloudUrl(urlString) { } const scheme = parsed.protocol.replace(/:$/, '').toLowerCase(); - if (BLOCKED_SCHEMES.has(scheme)) return { allowed: false }; + if (BLOCKED_SCHEMES.has(scheme) || !ALLOWED_SCHEMES.has(scheme)) return { allowed: false }; const hostname = parsed.hostname; if (isPrivateHost(hostname)) return { allowed: false }; @@ -99,4 +140,70 @@ function validateCloudUrl(urlString) { return { allowed: true }; } -module.exports = { validateCloudUrl, isPrivateHost }; +async function validateCloudUrlWithDns(urlString, options = {}) { + if (options.signal?.aborted) { + throw urlAbortError(options.signal); + } + const syntactic = validateCloudUrl(urlString); + if (!syntactic.allowed) return syntactic; + + const hostname = new URL(urlString).hostname.replace(/^\[|\]$/g, ''); + if (net.isIP(hostname)) return { allowed: !isPrivateHost(hostname) }; + + const lookup = options.lookup || dns.promises.lookup; + const timeoutMs = Math.max(100, Math.min(Number(options.timeoutMs) || 5000, 30_000)); + let timer = null; + let onAbort = null; + try { + const pending = [ + lookup(hostname, { all: true, verbatim: true }), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error('DNS lookup timed out.')), timeoutMs); + }), + ]; + if (options.signal) { + pending.push(new Promise((_, reject) => { + onAbort = () => { + reject(urlAbortError(options.signal)); + }; + options.signal.addEventListener('abort', onAbort, { once: true }); + })); + } + const addresses = await Promise.race(pending); + const resolved = Array.isArray(addresses) ? addresses : [addresses]; + if (resolved.length === 0) return { allowed: false }; + if (resolved.some((entry) => isPrivateHost(entry?.address || entry))) { + return { allowed: false }; + } + return { allowed: true }; + } catch (error) { + if (options.signal?.aborted) throw error; + return { allowed: false }; + } finally { + if (timer) clearTimeout(timer); + if (onAbort) options.signal?.removeEventListener('abort', onAbort); + } +} + +async function validateAndroidIntentUrl(urlString, options = {}) { + if (!urlString || typeof urlString !== 'string') return { allowed: false }; + let parsed; + try { + parsed = new URL(urlString); + } catch { + return { allowed: false }; + } + const scheme = parsed.protocol.replace(/:$/, '').toLowerCase(); + if (BLOCKED_ANDROID_INTENT_SCHEMES.has(scheme)) return { allowed: false }; + if (ALLOWED_SCHEMES.has(scheme)) { + return validateCloudUrlWithDns(urlString, options); + } + return { allowed: /^[a-z][a-z0-9+.-]*$/.test(scheme) }; +} + +module.exports = { + validateAndroidIntentUrl, + validateCloudUrl, + validateCloudUrlWithDns, + isPrivateHost, +}; diff --git a/server/utils/files.js b/server/utils/files.js new file mode 100644 index 00000000..ced513a2 --- /dev/null +++ b/server/utils/files.js @@ -0,0 +1,31 @@ +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); +const { randomUUID } = require('node:crypto'); +const { createAbortError, throwIfAborted } = require('./abort'); + +async function writeBufferAtomic(destination, data, options = {}) { + const target = String(destination || '').trim(); + if (!target) throw new Error('Atomic file destination is required.'); + const resolved = path.resolve(target); + const temporary = `${resolved}.${process.pid}.${randomUUID()}.tmp`; + throwIfAborted(options.signal, 'File write was aborted.'); + try { + await fs.promises.writeFile(temporary, data, { + flag: 'wx', + mode: options.mode ?? 0o600, + signal: options.signal, + }); + throwIfAborted(options.signal, 'File write was aborted.'); + await fs.promises.rename(temporary, resolved); + return resolved; + } catch (error) { + if (options.signal?.aborted) throw createAbortError(options.signal, 'File write was aborted.'); + throw error; + } finally { + await fs.promises.rm(temporary, { force: true }).catch(() => {}); + } +} + +module.exports = { writeBufferAtomic }; diff --git a/server/utils/image_payload.js b/server/utils/image_payload.js new file mode 100644 index 00000000..de3d6db7 --- /dev/null +++ b/server/utils/image_payload.js @@ -0,0 +1,95 @@ +'use strict'; + +const MAX_SCREENSHOT_BYTES = 20 * 1024 * 1024; +const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +function imageError(message, code = 'INVALID_IMAGE_PAYLOAD') { + const error = new Error(message); + error.code = code; + return error; +} + +function detectImage(buffer) { + if ( + buffer.length >= 24 + && buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE) + && buffer.subarray(12, 16).toString('ascii') === 'IHDR' + ) { + const width = buffer.readUInt32BE(16); + const height = buffer.readUInt32BE(20); + if (!width || !height || width > 32768 || height > 32768 || width * height > 150_000_000) { + throw imageError('Screenshot PNG has invalid or unsafe dimensions.'); + } + return { contentType: 'image/png', extension: 'png', width, height }; + } + if ( + buffer.length >= 4 + && buffer[0] === 0xff + && buffer[1] === 0xd8 + && buffer[buffer.length - 2] === 0xff + && buffer[buffer.length - 1] === 0xd9 + ) { + return { contentType: 'image/jpeg', extension: 'jpg', width: null, height: null }; + } + throw imageError('Screenshot payload is not a valid PNG or JPEG image.'); +} + +function validateImageBuffer(value, options = {}) { + const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value || []); + const maxBytes = Math.max(1024, Number(options.maxBytes) || MAX_SCREENSHOT_BYTES); + if (!buffer.length) throw imageError('Screenshot payload is empty.'); + if (buffer.length > maxBytes) { + throw imageError( + `Screenshot payload exceeds the ${maxBytes}-byte limit.`, + 'IMAGE_PAYLOAD_TOO_LARGE', + ); + } + const detected = detectImage(buffer); + const allowedTypes = Array.isArray(options.allowedTypes) && options.allowedTypes.length + ? new Set(options.allowedTypes.map((type) => String(type).toLowerCase())) + : null; + if (allowedTypes && !allowedTypes.has(detected.contentType)) { + throw imageError(`Screenshot type ${detected.contentType} is not allowed.`); + } + return { buffer, ...detected }; +} + +function decodeBase64Image(value, options = {}) { + const text = String(value || ''); + if (!text) throw imageError('Screenshot payload is empty.'); + let declaredType = null; + let encoded = text; + if (text.startsWith('data:')) { + const match = text.match(/^data:image\/(png|jpeg|jpg);base64,([A-Za-z0-9+/]*={0,2})$/i); + if (!match) throw imageError('Screenshot data URL is malformed or uses an unsupported type.'); + declaredType = match[1].toLowerCase() === 'png' ? 'image/png' : 'image/jpeg'; + encoded = match[2]; + } + if (!encoded || encoded.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) { + throw imageError('Screenshot base64 payload is malformed.'); + } + const maxBytes = Math.max(1024, Number(options.maxBytes) || MAX_SCREENSHOT_BYTES); + const padding = encoded.endsWith('==') ? 2 : (encoded.endsWith('=') ? 1 : 0); + const estimatedBytes = Math.floor(encoded.length * 3 / 4) - padding; + if (estimatedBytes > maxBytes) { + throw imageError( + `Screenshot payload exceeds the ${maxBytes}-byte limit.`, + 'IMAGE_PAYLOAD_TOO_LARGE', + ); + } + const buffer = Buffer.from(encoded, 'base64'); + if (buffer.toString('base64').replace(/=+$/, '') !== encoded.replace(/=+$/, '')) { + throw imageError('Screenshot base64 payload is malformed.'); + } + const validated = validateImageBuffer(buffer, { ...options, maxBytes }); + if (declaredType && declaredType !== validated.contentType) { + throw imageError('Screenshot data URL type does not match its image bytes.'); + } + return validated; +} + +module.exports = { + MAX_SCREENSHOT_BYTES, + decodeBase64Image, + validateImageBuffer, +}; diff --git a/server/utils/retry.js b/server/utils/retry.js new file mode 100644 index 00000000..9e031273 --- /dev/null +++ b/server/utils/retry.js @@ -0,0 +1,107 @@ +'use strict'; + +const { createAbortError, isAbortError } = require('./abort'); + +const RETRYABLE_HTTP_STATUS = new Set([ + 408, 409, 425, 429, 500, 502, 503, 504, 520, 521, 522, 524, 529, +]); +const RETRYABLE_NETWORK_CODES = new Set([ + 'ECONNRESET', + 'ECONNREFUSED', + 'ETIMEDOUT', + 'EPIPE', + 'EAI_AGAIN', + 'ENOTFOUND', + 'ENETUNREACH', + 'EHOSTUNREACH', + 'EAGAIN', + 'UND_ERR_CONNECT_TIMEOUT', + 'UND_ERR_SOCKET', + 'UND_ERR_HEADERS_TIMEOUT', + 'INTEGRATION_HTTP_TIMEOUT', +]); + +function getHttpStatus(error) { + if (!error || typeof error !== 'object') return null; + const candidates = [ + error.status, + error.statusCode, + error.response?.status, + error.cause?.status, + ]; + for (const value of candidates) { + const status = Number(value); + if (Number.isFinite(status) && status >= 100 && status < 600) return status; + } + return null; +} + +function getErrorCode(error) { + if (!error || typeof error !== 'object') return null; + return error.code || error.errno || error.cause?.code || null; +} + +function readHeader(headers, name) { + if (!headers) return undefined; + if (typeof headers.get === 'function') return headers.get(name); + return headers[name] ?? headers[name.toLowerCase()]; +} + +function retryAfterMilliseconds(headers, now = Date.now()) { + const milliseconds = readHeader(headers, 'retry-after-ms'); + if (milliseconds !== undefined && milliseconds !== null && String(milliseconds).trim()) { + const parsed = Number(milliseconds); + if (Number.isFinite(parsed) && parsed >= 0) return parsed; + } + + const retryAfter = readHeader(headers, 'retry-after'); + if (retryAfter === undefined || retryAfter === null || !String(retryAfter).trim()) { + return null; + } + const seconds = Number(retryAfter); + if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000; + const date = Date.parse(String(retryAfter)); + return Number.isFinite(date) ? Math.max(0, date - now) : null; +} + +function computeBackoffMs(attempt, baseDelayMs, maxDelayMs) { + const exp = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)); + return Math.round(exp / 2 + Math.random() * (exp / 2)); +} + +function isTransientIoError(error) { + if (!error || isAbortError(error)) return false; + const status = getHttpStatus(error); + if (status !== null) return RETRYABLE_HTTP_STATUS.has(status); + const code = getErrorCode(error); + return Boolean(code && RETRYABLE_NETWORK_CODES.has(String(code))); +} + +function abortableDelay(milliseconds, signal = null) { + if (signal?.aborted) return Promise.reject(createAbortError(signal)); + return new Promise((resolve, reject) => { + let timer = null; + const onAbort = () => { + if (timer) clearTimeout(timer); + signal?.removeEventListener('abort', onAbort); + reject(createAbortError(signal)); + }; + timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve(); + }, Math.max(0, Number(milliseconds) || 0)); + signal?.addEventListener('abort', onAbort, { once: true }); + }); +} + +module.exports = { + RETRYABLE_HTTP_STATUS, + RETRYABLE_NETWORK_CODES, + abortableDelay, + computeBackoffMs, + getErrorCode, + getHttpStatus, + isTransientIoError, + readHeader, + retryAfterMilliseconds, +}; diff --git a/test/backend/unit/agent_improvements.test.js b/test/backend/unit/agent_improvements.test.js index ec69873c..f1eec559 100644 --- a/test/backend/unit/agent_improvements.test.js +++ b/test/backend/unit/agent_improvements.test.js @@ -184,6 +184,34 @@ test('loop policy keeps the iteration ceiling high and relies on the read-only n assert.equal(clamped.maxConsecutiveReadOnlyIterations, 25); }); +test('loop policy supports bounded agent settings and per-run overrides', () => { + const configured = buildLoopPolicy({ + max_iterations: 320, + max_consecutive_read_only_iterations: 14, + max_consecutive_tool_failures: 12, + max_model_failure_recoveries: 7, + compaction_threshold: 0.72, + }); + assert.equal(configured.maxIterations, 320); + assert.equal(configured.maxConsecutiveReadOnlyIterations, 14); + assert.equal(configured.maxConsecutiveToolFailures, 12); + assert.equal(configured.maxModelFailureRecoveries, 7); + assert.equal(configured.compactionThreshold, 0.72); + + const overridden = buildLoopPolicy(configured, 'messaging', 'execute', { + maxIterations: 350, + maxConsecutiveReadOnlyIterations: 18, + maxConsecutiveToolFailures: 16, + maxModelFailureRecoveries: 9, + compactionThreshold: 0.9, + }); + assert.equal(overridden.maxIterations, 350); + assert.equal(overridden.maxConsecutiveReadOnlyIterations, 18); + assert.equal(overridden.maxConsecutiveToolFailures, 16); + assert.equal(overridden.maxModelFailureRecoveries, 9); + assert.equal(overridden.compactionThreshold, 0.9); +}); + test('create_task accepts schedule config as object, JSON string, or bare cron', () => { // Canonical object shape. const obj = resolveTaskTriggerArgs({ diff --git a/test/backend/unit/agent_loop_hook_stop.test.js b/test/backend/unit/agent_loop_hook_stop.test.js new file mode 100644 index 00000000..bdb31782 --- /dev/null +++ b/test/backend/unit/agent_loop_hook_stop.test.js @@ -0,0 +1,79 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { createTestRuntime, createTestUser, teardownTestRuntime } = require('../../helpers/db'); + +test('an on_loop_iteration stop is terminally recorded as stopped, never completed', async () => { + const ctx = createTestRuntime(); + const models = require('../../../server/services/ai/models'); + const { globalHooks } = require('../../../server/services/ai/hooks'); + const originalGetSupportedModels = models.getSupportedModels; + const originalCreateProviderInstance = models.createProviderInstance; + const hookId = globalHooks.register('on_loop_iteration', async () => ({ + stop: true, + reason: 'Stopped by reliability policy.', + }), { id: 'test-reliability-stop' }); + let engine; + + try { + const user = await createTestUser(ctx.db, { username: 'hook_stop_user' }); + let modelCalls = 0; + models.getSupportedModels = async () => [{ + id: 'test-model', + name: 'Test model', + provider: 'test-provider', + available: true, + purpose: 'general', + priceTier: 'free', + }]; + models.createProviderInstance = () => ({ + getContextWindow: () => 128000, + async chat() { + modelCalls += 1; + return { content: 'This should never be returned.', toolCalls: [] }; + }, + }); + + const { AgentEngine } = require('../../../server/services/ai/engine'); + engine = new AgentEngine(null); + engine.emit = () => {}; + engine.startMessagingProgressSupervisor = () => {}; + engine.stopMessagingProgressSupervisor = () => {}; + engine.buildSystemPrompt = async () => ({ stable: 'You are a test agent.', dynamic: '' }); + engine.getAvailableTools = () => []; + engine.persistPromptMetrics = async () => {}; + + const runId = 'hook-stop-run'; + const result = await engine.run(user.userId, 'Run until policy stops you.', { + runId, + stream: false, + skipTaskAnalysis: true, + skipDeliverableWorkflow: true, + forceMode: 'execute', + skipGlobalRecall: true, + skipConversationHistory: true, + skipConversationMaintenance: true, + skipVerifier: true, + bypassUserRateLimits: true, + }); + + assert.equal(result.status, 'stopped'); + assert.equal(result.content, ''); + assert.equal(modelCalls, 0); + assert.deepEqual( + ctx.db.prepare('SELECT status, error, final_response FROM agent_runs WHERE id = ?').get(runId), + { + status: 'stopped', + error: 'Stopped by reliability policy.', + final_response: null, + }, + ); + } finally { + globalHooks.deregister('on_loop_iteration', hookId); + models.getSupportedModels = originalGetSupportedModels; + models.createProviderInstance = originalCreateProviderInstance; + teardownTestRuntime(ctx); + } +}); diff --git a/test/backend/unit/agent_run_lifecycle.test.js b/test/backend/unit/agent_run_lifecycle.test.js index f8c2ed0a..245596a0 100644 --- a/test/backend/unit/agent_run_lifecycle.test.js +++ b/test/backend/unit/agent_run_lifecycle.test.js @@ -9,10 +9,12 @@ let ctx; let lifecycle; let AgentEngine; let userId; +let otherUserId; before(async () => { ctx = createTestRuntime(); userId = (await createTestUser(ctx.db, { username: 'run_lifecycle_user' })).userId; + otherUserId = (await createTestUser(ctx.db, { username: 'run_lifecycle_other' })).userId; lifecycle = require('../../../server/services/ai/loop/lifecycle'); ({ AgentEngine } = require('../../../server/services/ai/engine')); }); @@ -131,3 +133,156 @@ test('stopping a paused run releases its suspended execution', async () => { assert.equal(ctx.db.prepare('SELECT status FROM agent_runs WHERE id = ?').get('pause-stop-run').status, 'stopped'); engine.activeRuns.delete('pause-stop-run'); }); + +test('abort enforces ownership for persisted runs that are not active in memory', () => { + insertRun('persisted-owner-run'); + const engine = new AgentEngine(null); + + assert.equal(engine.abort('persisted-owner-run', { userId: otherUserId }), false); + assert.equal( + ctx.db.prepare('SELECT status FROM agent_runs WHERE id = ?').get('persisted-owner-run').status, + 'running', + ); + + assert.equal(engine.abort('persisted-owner-run', { + userId, + reason: 'Owner requested stop.', + }), true); + assert.deepEqual( + ctx.db.prepare('SELECT status, error FROM agent_runs WHERE id = ?').get('persisted-owner-run'), + { status: 'stopped', error: 'Owner requested stop.' }, + ); +}); + +test('abort rejects unknown and already-terminal runs', () => { + insertRun('already-complete-run', 'completed'); + const engine = new AgentEngine(null); + + assert.equal(engine.abort('missing-run', { userId }), false); + assert.equal(engine.abort('already-complete-run', { userId }), false); +}); + +test('engine shutdown aborts and awaits owned background work', async () => { + const engine = new AgentEngine(null); + let observedReason = null; + const backgroundTask = engine.trackBackgroundTask((signal) => new Promise((resolve) => { + signal.addEventListener('abort', () => { + observedReason = signal.reason; + resolve('cancelled'); + }, { once: true }); + })); + await new Promise((resolve) => setImmediate(resolve)); + + const status = await engine.shutdown({ reason: 'test engine shutdown', timeoutMs: 1000 }); + + assert.deepEqual(status, { state: 'stopped', timedOut: false, pendingCount: 0 }); + assert.equal(await backgroundTask, 'cancelled'); + assert.equal(observedReason, 'test engine shutdown'); + assert.equal(engine.backgroundTasks.size, 0); +}); + +test('engine rejects new foreground and background work after shutdown starts', async () => { + const engine = new AgentEngine(null); + await engine.shutdown({ reason: 'engine no longer accepting work' }); + + await assert.rejects( + engine.trackBackgroundTask(async () => 'too late'), + (error) => error.name === 'AbortError' + && error.message === 'engine no longer accepting work', + ); + await assert.rejects( + engine.runWithModel(userId, 'Do not start this run.'), + (error) => error.name === 'AbortError' + && error.message === 'engine no longer accepting work', + ); +}); + +test('engine shutdown fences and drains a sub-agent startup waiting on memory', async () => { + insertRun('subagent-startup-parent'); + let markRecallStarted; + let releaseRecall; + const recallStarted = new Promise((resolve) => { + markRecallStarted = resolve; + }); + const recallBarrier = new Promise((resolve) => { + releaseRecall = resolve; + }); + const engine = new AgentEngine(null, { + memoryManager: { + recallMemory: async () => { + markRecallStarted(); + await recallBarrier; + return []; + }, + }, + }); + engine.emit = () => {}; + engine.activeRuns.set('subagent-startup-parent', { + userId, + agentId: null, + status: 'running', + aborted: false, + subagentDepth: 0, + abortController: new AbortController(), + toolPids: new Set(), + }); + const spawn = engine.spawnSubagent( + userId, + 'subagent-startup-parent', + 'Inspect the pending task.', + ); + + await recallStarted; + const stopping = engine.shutdown({ + reason: 'shutdown while child startup was pending', + timeoutMs: 1000, + }); + releaseRecall(); + + await assert.rejects( + spawn, + (error) => error.name === 'AbortError' + && error.message === 'shutdown while child startup was pending', + ); + assert.deepEqual( + await stopping, + { state: 'stopped', timedOut: false, pendingCount: 0 }, + ); + assert.equal(engine.subagentStartupTasks.size, 0); + assert.equal(engine.subagents.size, 0); + engine.activeRuns.delete('subagent-startup-parent'); +}); + +test('parent cleanup waits for child shutdown and suppresses orphan rejection', async () => { + const engine = new AgentEngine(null); + let finishShutdown; + const childShutdown = new Promise((resolve) => { + finishShutdown = resolve; + }); + const record = { + handle: 'child-handle', + parentRunId: 'parent-run', + childRunId: 'child-run', + userId, + status: 'running', + settled: false, + promise: Promise.resolve(), + engine: { + shutdown: () => childShutdown, + }, + }; + engine.emit = () => {}; + engine.subagents.set(record.handle, record); + + let cleanupSettled = false; + const cleanup = engine.cleanupSubagentsForRun('parent-run').then((result) => { + cleanupSettled = true; + return result; + }); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(cleanupSettled, false); + assert.equal(record.status, 'cancelled'); + finishShutdown({ state: 'stopped', timedOut: false }); + + assert.deepEqual(await cleanup, { cancelled: 1, timedOut: 0 }); +}); diff --git a/test/backend/unit/agent_run_startup_abort.test.js b/test/backend/unit/agent_run_startup_abort.test.js new file mode 100644 index 00000000..4dd81573 --- /dev/null +++ b/test/backend/unit/agent_run_startup_abort.test.js @@ -0,0 +1,109 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { createTestRuntime, createTestUser, teardownTestRuntime } = require('../../helpers/db'); + +test('a run can be stopped while model discovery is still pending', async () => { + const ctx = createTestRuntime(); + const models = require('../../../server/services/ai/models'); + const originalGetSupportedModels = models.getSupportedModels; + let signalSeen; + let notifyStarted; + const started = new Promise((resolve) => { notifyStarted = resolve; }); + + try { + const user = await createTestUser(ctx.db, { username: 'startup_abort_user' }); + models.getSupportedModels = async (_userId, _agentId, options = {}) => { + signalSeen = options.signal; + notifyStarted(); + return new Promise((_, reject) => { + const onAbort = () => { + const error = new Error(String(options.signal?.reason || 'aborted')); + error.name = 'AbortError'; + error.code = 'ABORT_ERR'; + reject(error); + }; + if (options.signal?.aborted) onAbort(); + else options.signal?.addEventListener('abort', onAbort, { once: true }); + }); + }; + + const { AgentEngine } = require('../../../server/services/ai/engine'); + const engine = new AgentEngine(null); + engine.emit = () => {}; + const runId = 'startup-abort-run'; + const run = engine.run(user.userId, 'Do something after discovery.', { + runId, + bypassUserRateLimits: true, + }); + + await started; + assert.equal(engine.activeRuns.has(runId), true); + assert.equal(engine.abort(runId, { + userId: user.userId, + reason: 'Stopped during provider discovery.', + }), true); + assert.equal(signalSeen.aborted, true); + + const result = await run; + assert.equal(result.status, 'stopped'); + assert.equal(engine.activeRuns.has(runId), false); + assert.deepEqual( + ctx.db.prepare('SELECT status, error FROM agent_runs WHERE id = ?').get(runId), + { + status: 'stopped', + error: 'Stopped during provider discovery.', + }, + ); + } finally { + models.getSupportedModels = originalGetSupportedModels; + teardownTestRuntime(ctx); + } +}); + +test('a caller AbortSignal interrupts a run while model discovery is pending', async () => { + const ctx = createTestRuntime(); + const models = require('../../../server/services/ai/models'); + const originalGetSupportedModels = models.getSupportedModels; + let notifyStarted; + const started = new Promise((resolve) => { notifyStarted = resolve; }); + + try { + const user = await createTestUser(ctx.db, { username: 'external_abort_user' }); + models.getSupportedModels = async (_userId, _agentId, options = {}) => { + notifyStarted(); + return new Promise((_, reject) => { + options.signal.addEventListener('abort', () => reject(options.signal.reason), { + once: true, + }); + }); + }; + + const { AgentEngine } = require('../../../server/services/ai/engine'); + const engine = new AgentEngine(null); + engine.emit = () => {}; + const controller = new AbortController(); + const runId = 'external-startup-abort-run'; + const run = engine.run(user.userId, 'Do something after discovery.', { + runId, + signal: controller.signal, + bypassUserRateLimits: true, + }); + + await started; + controller.abort('Scheduled task runtime stopped.'); + const result = await run; + + assert.equal(result.status, 'interrupted'); + assert.equal(engine.activeRuns.has(runId), false); + assert.equal( + ctx.db.prepare('SELECT status FROM agent_runs WHERE id = ?').get(runId).status, + 'interrupted', + ); + } finally { + models.getSupportedModels = originalGetSupportedModels; + teardownTestRuntime(ctx); + } +}); diff --git a/test/backend/unit/agent_run_startup_failure.test.js b/test/backend/unit/agent_run_startup_failure.test.js new file mode 100644 index 00000000..4476ea00 --- /dev/null +++ b/test/backend/unit/agent_run_startup_failure.test.js @@ -0,0 +1,73 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { createTestRuntime, createTestUser, teardownTestRuntime } = require('../../helpers/db'); + +test('provider-selection failures leave a durable failed run instead of disappearing', async () => { + const ctx = createTestRuntime(); + const models = require('../../../server/services/ai/models'); + const originalGetSupportedModels = models.getSupportedModels; + try { + const user = await createTestUser(ctx.db, { username: 'startup_failure_user' }); + models.getSupportedModels = async () => []; + const { AgentEngine } = require('../../../server/services/ai/engine'); + const engine = new AgentEngine(null); + engine.emit = () => {}; + + await assert.rejects( + engine.run(user.userId, 'Do something.', { + runId: 'provider-selection-failure', + bypassUserRateLimits: true, + }), + /No AI providers are currently available/, + ); + + const row = ctx.db.prepare( + 'SELECT status, error, completed_at FROM agent_runs WHERE id = ?', + ).get('provider-selection-failure'); + assert.equal(row.status, 'failed'); + assert.match(row.error, /No AI providers are currently available/); + assert.ok(row.completed_at); + assert.equal(engine.activeRuns.has('provider-selection-failure'), false); + } finally { + models.getSupportedModels = originalGetSupportedModels; + teardownTestRuntime(ctx); + } +}); + +test('duplicate run ids are rejected without reviving or mutating the original run', async () => { + const ctx = createTestRuntime(); + try { + const user = await createTestUser(ctx.db, { username: 'duplicate_run_user' }); + ctx.db.prepare( + `INSERT INTO agent_runs ( + id, user_id, title, status, model, final_response, completed_at + ) VALUES (?, ?, ?, 'completed', ?, ?, datetime('now'))`, + ).run('duplicate-run', user.userId, 'Original', 'test-model', 'Original result.'); + + const { AgentEngine } = require('../../../server/services/ai/engine'); + const engine = new AgentEngine(null); + await assert.rejects( + engine.run(user.userId, 'A duplicate request.', { + runId: 'duplicate-run', + bypassUserRateLimits: true, + }), + (error) => error.code === 'RUN_ID_CONFLICT', + ); + + assert.deepEqual( + ctx.db.prepare( + 'SELECT status, model, final_response FROM agent_runs WHERE id = ?', + ).get('duplicate-run'), + { + status: 'completed', + model: 'test-model', + final_response: 'Original result.', + }, + ); + } finally { + teardownTestRuntime(ctx); + } +}); diff --git a/test/backend/unit/ai_settings_policy.test.js b/test/backend/unit/ai_settings_policy.test.js new file mode 100644 index 00000000..ba222a74 --- /dev/null +++ b/test/backend/unit/ai_settings_policy.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, test } = require('node:test'); + +const { + createTestRuntime, + createTestUser, + teardownTestRuntime, +} = require('../../helpers/db'); + +let ctx; + +afterEach(() => { + teardownTestRuntime(ctx); + ctx = null; +}); + +test('agent loop policy settings persist, normalize, and preserve contextual defaults', async () => { + ctx = createTestRuntime(); + const user = await createTestUser(ctx.db, { username: 'loop_policy_settings' }); + const { resolveAgentId } = require('../../../server/services/agents/manager'); + const { + ensureDefaultAiSettings, + getAiSettings, + } = require('../../../server/services/ai/settings'); + const { buildLoopPolicy } = require('../../../server/services/ai/loopPolicy'); + const agentId = resolveAgentId(user.userId, null); + + const defaults = ensureDefaultAiSettings(user.userId, agentId); + assert.equal(defaults.max_iterations, null); + assert.equal(defaults.max_consecutive_read_only_iterations, null); + assert.equal(buildLoopPolicy(defaults, 'tasks', 'execute', { widgetId: 'widget-1' }).maxIterations, 150); + + const upsert = ctx.db.prepare( + `INSERT INTO agent_settings (user_id, agent_id, key, value) + VALUES (?, ?, ?, ?) + ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value`, + ); + const configured = { + max_iterations: 999, + max_consecutive_read_only_iterations: 2, + max_consecutive_tool_failures: 99, + max_model_failure_recoveries: -1, + compaction_threshold: 0.05, + tool_replay_budget_file_chars: 750, + }; + for (const [key, value] of Object.entries(configured)) { + upsert.run(user.userId, agentId, key, JSON.stringify(value)); + } + + const settings = getAiSettings(user.userId, agentId); + assert.equal(settings.max_iterations, 400); + assert.equal(settings.max_consecutive_read_only_iterations, 3); + assert.equal(settings.max_consecutive_tool_failures, 50); + assert.equal(settings.max_model_failure_recoveries, 0); + assert.equal(settings.compaction_threshold, 0.1); + assert.equal(settings.tool_replay_budget_file_chars, 750); + + const policy = buildLoopPolicy(settings, 'messaging', 'execute'); + assert.equal(policy.maxIterations, 400); + assert.equal(policy.maxConsecutiveReadOnlyIterations, 3); + assert.equal(policy.maxConsecutiveToolFailures, 50); + assert.equal(policy.maxModelFailureRecoveries, 0); + assert.equal(policy.compactionThreshold, 0.1); + assert.equal(policy.toolResultBudget.file, 750); +}); diff --git a/test/backend/unit/android_controller.test.js b/test/backend/unit/android_controller.test.js new file mode 100644 index 00000000..120ae1cf --- /dev/null +++ b/test/backend/unit/android_controller.test.js @@ -0,0 +1,190 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { after, before, test } = require('node:test'); + +const { createTestRuntime, teardownTestRuntime } = require('../../helpers/db'); + +let ctx; +let AndroidController; +let findBestNode; +let parseUiDump; + +before(() => { + ctx = createTestRuntime(); + ({ AndroidController } = require('../../../server/services/android/controller')); + ({ findBestNode, parseUiDump } = require('../../../server/services/android/uia')); +}); + +after(() => teardownTestRuntime(ctx)); + +function sampleNodes() { + return [ + { + text: 'Continue', + resourceId: 'com.example:id/continue', + description: 'Continue setup', + className: 'android.widget.Button', + packageName: 'com.example', + clickable: true, + enabled: true, + bounds: { + left: 10, + top: 20, + right: 210, + bottom: 100, + width: 200, + height: 80, + centerX: 110, + centerY: 60, + }, + }, + ]; +} + +test('UI dump parser includes nested container nodes and enforces clickable selectors', () => { + const xml = [ + '', + '', + '', + '', + '', + ].join(''); + const nodes = parseUiDump(xml); + + assert.equal(nodes.length, 2); + assert.equal(findBestNode(nodes, { text: 'Container' }).resourceId, 'container'); + assert.equal(findBestNode(nodes, { text: 'Container', clickable: true }), null); + assert.equal(findBestNode(nodes, { text: 'Continue', clickable: true }).bounds.centerX, 110); +}); + +test('selector-based tap resolves the UI node center and returns fresh evidence', async () => { + const controller = new AndroidController({ userId: '7', sdkDir: ctx.dir }); + const commands = []; + controller.dumpUi = async () => ({ nodes: sampleNodes() }); + controller.shell = async (value) => { + commands.push(typeof value === 'string' ? value : value.command); + return ''; + }; + controller.screenshot = async () => ({ screenshotPath: '/artifacts/after.png' }); + + const result = await controller.tap({ resourceId: 'com.example:id/continue', clickable: true }); + + assert.deepEqual(commands, ['input tap 110 60']); + assert.equal(result.target.text, 'Continue'); + assert.equal(result.screenshotPath, '/artifacts/after.png'); +}); + +test('tap rejects partial or missing coordinates instead of touching 0,0', async () => { + const controller = new AndroidController({ userId: '7', sdkDir: ctx.dir }); + + await assert.rejects(controller.tap({ x: 10 }), /Both x and y/); + await assert.rejects(controller.tap({}), /coordinates or a UI selector/); +}); + +test('type focuses a selector, clears the field, and types escaped text', async () => { + const controller = new AndroidController({ userId: '7', sdkDir: ctx.dir }); + const commands = []; + controller.dumpUi = async () => ({ nodes: sampleNodes() }); + controller.shell = async (value) => { + commands.push(typeof value === 'string' ? value : value.command); + return ''; + }; + + const result = await controller.type({ + text: 'hello world', + resourceId: 'com.example:id/continue', + clear: true, + pressEnter: true, + }); + + assert.equal(commands[0], 'input tap 110 60'); + assert.match(commands[1], /KEYCODE_DEL/); + assert.equal(commands[2], "input text 'hello%sworld'"); + assert.equal(commands[3], 'input keyevent KEYCODE_ENTER'); + assert.equal(result.target.resourceId, 'com.example:id/continue'); +}); + +test('waitFor returns the matched node rather than emulator-only readiness', async () => { + const controller = new AndroidController({ userId: '7', sdkDir: ctx.dir }); + controller.dumpUi = async () => ({ nodes: sampleNodes() }); + controller.screenshot = async () => ({ screenshotPath: '/artifacts/match.png' }); + + const result = await controller.waitFor({ text: 'Continue', timeoutMs: 1000 }); + + assert.equal(result.found, true); + assert.equal(result.node.text, 'Continue'); + assert.equal(result.screenshotPath, '/artifacts/match.png'); +}); + +test('startEmulator exists for agent tools and waits for boot completion', async () => { + const controller = new AndroidController({ userId: '7', sdkDir: ctx.dir }); + const calls = []; + controller.requestStartEmulator = async (options) => { + calls.push({ phase: 'request', options }); + return { success: true, pending: true, bootstrapped: false }; + }; + controller.waitForDevice = async (options) => { + calls.push({ phase: 'wait', options }); + return 'emulator-5554'; + }; + + const result = await controller.startEmulator({ headless: false, timeoutMs: 12_345 }); + + assert.deepEqual(result, { + success: true, + pending: false, + bootstrapped: true, + adbSerial: 'emulator-5554', + }); + assert.equal(calls[0].options.headless, false); + assert.equal(calls[1].options.timeoutMs, 12_345); +}); + +test('pressKey rejects shell metacharacters', async () => { + const controller = new AndroidController({ userId: '7', sdkDir: ctx.dir }); + controller.shell = async () => { throw new Error('shell should not run'); }; + + await assert.rejects(controller.pressKey('HOME; reboot'), /Unsupported Android key/); +}); + +test('Android screenshots reject corrupt ADB output before artifact creation', async () => { + const controller = new AndroidController({ + userId: '7', + sdkDir: ctx.dir, + artifactStore: { + async createBufferArtifact() { + throw new Error('artifact creation should not run'); + }, + }, + }); + controller.capturePng = async () => Buffer.from('adb error text'); + + await assert.rejects(controller.screenshot(), /not a valid PNG or JPEG/i); +}); + +test('Android screenshots are written through the bounded artifact path', async () => { + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', + 'base64', + ); + let artifactOptions = null; + const controller = new AndroidController({ + userId: '7', + sdkDir: ctx.dir, + artifactStore: { + async createBufferArtifact(_userId, options) { + artifactOptions = options; + return { url: '/api/artifacts/android-shot/content' }; + }, + }, + }); + controller.capturePng = async () => png; + + assert.deepEqual( + await controller.screenshot(), + { screenshotPath: '/api/artifacts/android-shot/content' }, + ); + assert.equal(artifactOptions.contentType, 'image/png'); + assert.deepEqual(artifactOptions.content, png); +}); diff --git a/test/backend/unit/android_process.test.js b/test/backend/unit/android_process.test.js new file mode 100644 index 00000000..16a3fce1 --- /dev/null +++ b/test/backend/unit/android_process.test.js @@ -0,0 +1,45 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { runProcess } = require('../../../server/services/android/process'); + +test('Android subprocess runner captures successful output', async () => { + const result = await runProcess(process.execPath, ['-e', 'process.stdout.write("ready")']); + + assert.equal(result.exitCode, 0); + assert.equal(result.stdout, 'ready'); + assert.equal(result.stderr, ''); +}); + +test('Android subprocess runner terminates commands at the deadline', async () => { + await assert.rejects( + runProcess(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { timeoutMs: 100 }), + (error) => error.code === 'PROCESS_TIMEOUT' && error.timeoutMs === 100, + ); +}); + +test('Android subprocess runner bounds command output', async () => { + await assert.rejects( + runProcess( + process.execPath, + ['-e', 'process.stdout.write("x".repeat(4096))'], + { maxOutputBytes: 1024 }, + ), + (error) => error.code === 'PROCESS_OUTPUT_LIMIT', + ); +}); + +test('Android subprocess runner forwards cancellation', async () => { + const controller = new AbortController(); + const command = runProcess( + process.execPath, + ['-e', 'setInterval(() => {}, 1000)'], + { signal: controller.signal, timeoutMs: 5000 }, + ); + const reason = new Error('stop Android subprocess'); + controller.abort(reason); + + await assert.rejects(command, (error) => error === reason); +}); diff --git a/test/backend/unit/android_sdk_download.test.js b/test/backend/unit/android_sdk_download.test.js new file mode 100644 index 00000000..21d17d5a --- /dev/null +++ b/test/backend/unit/android_sdk_download.test.js @@ -0,0 +1,118 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const crypto = require('node:crypto'); +const { EventEmitter } = require('node:events'); +const fs = require('node:fs'); +const https = require('node:https'); +const os = require('node:os'); +const path = require('node:path'); +const { Readable } = require('node:stream'); +const { afterEach, test } = require('node:test'); + +const { + downloadFile, + resolveCommandLineToolsRelease, +} = require('../../../server/services/android/sdk_download'); + +const originalHttpsGet = https.get; + +afterEach(() => { + https.get = originalHttpsGet; +}); + +function mockDownload(payload) { + https.get = (_url, callback) => { + const request = new EventEmitter(); + request.setTimeout = () => {}; + request.destroy = (error) => { + if (error) queueMicrotask(() => request.emit('error', error)); + }; + queueMicrotask(() => { + const response = Readable.from([payload]); + response.statusCode = 200; + response.headers = { 'content-length': String(payload.length) }; + callback(response); + }); + return request; + }; +} + +test('resolves the current architecture-specific official command-line tools', () => { + const armMac = resolveCommandLineToolsRelease('darwin', 'arm64'); + const intelMac = resolveCommandLineToolsRelease('darwin', 'x64'); + const linux = resolveCommandLineToolsRelease('linux', 'x64'); + const windows = resolveCommandLineToolsRelease('win32', 'x64'); + + assert.match(armMac.url, /commandlinetools-mac_arm64-15859902_latest\.zip$/); + assert.match(intelMac.url, /commandlinetools-mac_x86_64-15859902_latest\.zip$/); + assert.match(linux.url, /commandlinetools-linux-15859902_latest\.zip$/); + assert.match(windows.url, /commandlinetools-win-15859902_latest\.zip$/); + for (const release of [armMac, intelMac, linux, windows]) { + assert.match(release.sha256, /^[a-f0-9]{64}$/); + } + assert.throws( + () => resolveCommandLineToolsRelease('darwin', 'ppc64'), + /macOS architecture/, + ); +}); + +test('publishes a download only after its SHA-256 checksum matches', async () => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'neoagent-sdk-download-test-')); + const destination = path.join(directory, 'tools.zip'); + const payload = Buffer.from('verified Android command-line tools'); + mockDownload(payload); + + try { + await downloadFile('https://dl.google.com/android/repository/tools.zip', destination, { + expectedSha256: crypto.createHash('sha256').update(payload).digest('hex'), + }); + assert.deepEqual(fs.readFileSync(destination), payload); + } finally { + fs.rmSync(directory, { recursive: true, force: true }); + } +}); + +test('rejects a checksum mismatch without leaving a published or partial archive', async () => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'neoagent-sdk-download-test-')); + const destination = path.join(directory, 'tools.zip'); + mockDownload(Buffer.from('tampered archive')); + + try { + await assert.rejects( + downloadFile('https://dl.google.com/android/repository/tools.zip', destination, { + expectedSha256: '0'.repeat(64), + }), + { code: 'ANDROID_SDK_CHECKSUM_MISMATCH' }, + ); + assert.equal(fs.existsSync(destination), false); + assert.deepEqual(fs.readdirSync(directory), []); + } finally { + fs.rmSync(directory, { recursive: true, force: true }); + } +}); + +test('preserves the caller abort reason while waiting for an SDK response', async () => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'neoagent-sdk-download-test-')); + const destination = path.join(directory, 'tools.zip'); + const controller = new AbortController(); + const reason = new Error('startup was cancelled'); + https.get = () => { + const request = new EventEmitter(); + request.setTimeout = () => {}; + request.destroy = (error) => queueMicrotask(() => request.emit('error', error)); + return request; + }; + + try { + const pending = downloadFile('https://dl.google.com/android/repository/tools.zip', destination, { + expectedSha256: '0'.repeat(64), + signal: controller.signal, + }); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.deepEqual(fs.readdirSync(directory), []); + } finally { + fs.rmSync(directory, { recursive: true, force: true }); + } +}); diff --git a/test/backend/unit/browser_controller_reliability.test.js b/test/backend/unit/browser_controller_reliability.test.js new file mode 100644 index 00000000..dfa8d450 --- /dev/null +++ b/test/backend/unit/browser_controller_reliability.test.js @@ -0,0 +1,186 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { EventEmitter } = require('node:events'); +const { test } = require('node:test'); + +const { BrowserController } = require('../../../server/services/browser/controller'); + +function controllerWithValidator(validator) { + return new BrowserController({ + userId: `browser-test-${process.pid}`, + urlValidator: validator, + }); +} + +test('browser network guard validates requests and WebSockets before connecting', async () => { + const handlers = {}; + const controller = controllerWithValidator(async (url) => ({ + allowed: !url.includes('blocked.example'), + })); + const context = { + async route(_pattern, handler) { handlers.request = handler; }, + async routeWebSocket(_pattern, handler) { handlers.webSocket = handler; }, + }; + await controller._installNetworkGuard(context); + + const allowedRoute = { + request: () => ({ url: () => 'https://public.example/app.js' }), + continued: 0, + aborted: 0, + async continue() { this.continued += 1; }, + async abort() { this.aborted += 1; }, + }; + const blockedRoute = { + request: () => ({ url: () => 'https://blocked.example/secret' }), + continued: 0, + aborted: 0, + async continue() { this.continued += 1; }, + async abort() { this.aborted += 1; }, + }; + await handlers.request(allowedRoute); + await handlers.request(blockedRoute); + assert.equal(allowedRoute.continued, 1); + assert.equal(allowedRoute.aborted, 0); + assert.equal(blockedRoute.continued, 0); + assert.equal(blockedRoute.aborted, 1); + + const allowedSocket = { + url: () => 'wss://public.example/socket', + connected: 0, + closed: 0, + connectToServer() { this.connected += 1; }, + async close() { this.closed += 1; }, + }; + const blockedSocket = { + url: () => 'ws://blocked.example/socket', + connected: 0, + closed: 0, + connectToServer() { this.connected += 1; }, + async close() { this.closed += 1; }, + }; + await handlers.webSocket(allowedSocket); + await handlers.webSocket(blockedSocket); + assert.equal(allowedSocket.connected, 1); + assert.equal(allowedSocket.closed, 0); + assert.equal(blockedSocket.connected, 0); + assert.equal(blockedSocket.closed, 1); +}); + +test('browser navigation only accepts validator-approved top-level URLs', async () => { + const controller = controllerWithValidator(async (url) => ({ + allowed: url === 'https://public.example/', + })); + + await controller._assertNavigationAllowed('https://public.example/'); + await assert.rejects( + controller._assertNavigationAllowed('data:text/html,private'), + (error) => error.code === 'URL_BLOCKED', + ); +}); + +test('aborting a browser action closes the page and rejects promptly', async () => { + const controller = controllerWithValidator(async () => ({ allowed: true })); + const abortController = new AbortController(); + let closed = false; + const page = { + isClosed: () => closed, + async close() { closed = true; }, + }; + const operation = controller._withPageCancellation( + page, + abortController.signal, + () => new Promise(() => {}), + ); + abortController.abort(); + + await assert.rejects(operation, (error) => error.name === 'AbortError'); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(closed, true); +}); + +test('browser lifecycle events discard crashed pages and disconnected contexts', () => { + const controller = controllerWithValidator(async () => ({ allowed: true })); + const context = new EventEmitter(); + context.pages = () => []; + context.isClosed = () => false; + const browser = new EventEmitter(); + browser.isConnected = () => true; + const page = new EventEmitter(); + page.isClosed = () => false; + + controller.context = context; + controller.browser = browser; + controller._bindContextLifecycle(context, browser); + controller._bindPage(page); + assert.equal(controller.isLaunched(), true); + + page.emit('crash'); + assert.equal(controller.page, null); + + browser.emit('disconnected'); + assert.equal(controller.context, null); + assert.equal(controller.browser, null); +}); + +test('browser point actions reject missing coordinates instead of clicking 0,0', async () => { + const controller = controllerWithValidator(async () => ({ allowed: true })); + let moves = 0; + controller.ensurePage = async () => ({ + isClosed: () => false, + mouse: { + async move() { moves += 1; }, + async down() {}, + async up() {}, + }, + url: () => 'https://example.com/', + async title() { return 'Example'; }, + }); + + const result = await controller.clickPoint(undefined, 20, false); + + assert.match(result.error, /x coordinate is required/i); + assert.equal(moves, 0); +}); + +test('browser evaluation output is bounded and reports truncation', async () => { + const controller = controllerWithValidator(async () => ({ allowed: true })); + const page = { + isClosed: () => false, + async evaluate() { return 'x'.repeat((1024 * 1024) + 50); }, + }; + controller.ensurePage = async () => page; + + const result = await controller.evaluate('"large"'); + + assert.equal(result.result.length, 1024 * 1024); + assert.equal(result.truncated, true); +}); + +test('browser cookie reads settle promptly with the caller cancellation reason', async () => { + const controller = controllerWithValidator(async () => ({ allowed: true })); + controller.ensureBrowser = async () => {}; + controller.context = { cookies: () => new Promise(() => {}) }; + const abortController = new AbortController(); + const reason = new Error('stop cookie export'); + const pending = controller.getCookies({ signal: abortController.signal }); + abortController.abort(reason); + + await assert.rejects(pending, (error) => error === reason); +}); + +test('current-referrer navigation surfaces a failed navigation wait', async () => { + const controller = controllerWithValidator(async () => ({ allowed: true })); + const waitError = new Error('navigation did not commit'); + const page = { + url: () => 'https://example.com/old', + waitForURL: async () => { throw waitError; }, + evaluate: async () => {}, + waitForLoadState: async () => {}, + }; + + await assert.rejects( + controller._navigatePage(page, 'https://example.com/new', { referrerMode: 'current' }), + (error) => error === waitError, + ); +}); diff --git a/test/backend/unit/browser_extension_http.test.js b/test/backend/unit/browser_extension_http.test.js new file mode 100644 index 00000000..9185e66e --- /dev/null +++ b/test/backend/unit/browser_extension_http.test.js @@ -0,0 +1,99 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const path = require('node:path'); +const { test } = require('node:test'); +const { pathToFileURL } = require('node:url'); + +async function extensionHttp() { + return import(pathToFileURL(path.resolve( + __dirname, + '../../../extensions/chrome-browser/http.mjs', + )).href); +} + +test('extension HTTP helper parses bounded JSON responses', async () => { + const { fetchJsonWithTimeout } = await extensionHttp(); + const result = await fetchJsonWithTimeout('https://neoagent.example.test/status', {}, { + fetchImpl: async () => new Response(JSON.stringify({ ready: true }), { status: 200 }), + }); + + assert.equal(result.response.status, 200); + assert.deepEqual(result.payload, { ready: true }); +}); + +test('extension HTTP helper cancels oversized response streams', async () => { + const { readJsonResponse } = await extensionHttp(); + let cancelled = false; + const chunks = [new Uint8Array(5), new Uint8Array(5)]; + const response = { + headers: { get: () => null }, + body: { + getReader() { + return { + async read() { + if (chunks.length === 0) return { done: true }; + return { done: false, value: chunks.shift() }; + }, + async cancel() { cancelled = true; }, + releaseLock() {}, + }; + }, + }, + }; + + await assert.rejects( + readJsonResponse(response, { maxResponseBytes: 8 }), + (error) => error.code === 'EXTENSION_RESPONSE_TOO_LARGE', + ); + assert.equal(cancelled, true); +}); + +test('extension HTTP timeout covers a stalled response body', async () => { + const { fetchJsonWithTimeout } = await extensionHttp(); + let cancelled = false; + const fetchImpl = async () => ({ + headers: { get: () => null }, + body: { + getReader() { + return { + read: () => new Promise(() => {}), + async cancel() { cancelled = true; }, + releaseLock() {}, + }; + }, + }, + }); + + await assert.rejects( + fetchJsonWithTimeout('https://neoagent.example.test/stalled', {}, { + fetchImpl, + timeoutMs: 10, + }), + (error) => error.code === 'EXTENSION_HTTP_TIMEOUT', + ); + assert.equal(cancelled, true); +}); + +test('extension HTTP helper preserves caller cancellation reasons', async () => { + const { fetchJsonWithTimeout } = await extensionHttp(); + const controller = new AbortController(); + const reason = new Error('extension operation stopped'); + let capturedSignal = null; + const fetchImpl = (_url, options) => { + capturedSignal = options.signal; + return new Promise(() => {}); + }; + const pending = fetchJsonWithTimeout( + 'https://neoagent.example.test/status', + { signal: controller.signal }, + { fetchImpl }, + ); + await new Promise((resolve) => setImmediate(resolve)); + controller.abort(reason); + + await assert.rejects(pending, (error) => error === reason); + assert.ok(capturedSignal); + assert.notEqual(capturedSignal, controller.signal); + assert.equal(capturedSignal.aborted, true); +}); diff --git a/test/backend/unit/browser_extension_reliability.test.js b/test/backend/unit/browser_extension_reliability.test.js new file mode 100644 index 00000000..08b4a2dd --- /dev/null +++ b/test/backend/unit/browser_extension_reliability.test.js @@ -0,0 +1,434 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { EventEmitter } = require('node:events'); +const path = require('node:path'); +const { test } = require('node:test'); +const { pathToFileURL } = require('node:url'); + +const { ExtensionBrowserProvider } = require('../../../server/services/browser/extension/provider'); +const { + BrowserExtensionRegistry, + ExtensionBrowserConnection, +} = require('../../../server/services/browser/extension/registry'); + +class FakeWebSocket extends EventEmitter { + constructor() { + super(); + this.readyState = 1; + this.messages = []; + } + + send(value) { + this.messages.push(JSON.parse(value)); + } + + ping() {} + + close() { + this.readyState = 3; + this.emit('close'); + } + + terminate() { + this.close(); + } +} + +function connectionFor(ws) { + return new ExtensionBrowserConnection({ + registry: { + touchPresence() {}, + unregisterConnection() {}, + }, + ws, + userId: 'user-1', + tokenId: 'token-1', + meta: {}, + timeoutMs: 5000, + heartbeatIntervalMs: 0, + heartbeatTimeoutMs: 0, + presenceTouchIntervalMs: 0, + }); +} + +function nextTurn() { + return new Promise((resolve) => setImmediate(resolve)); +} + +test('aborting an extension command cancels work in the real browser', async () => { + const ws = new FakeWebSocket(); + const connection = connectionFor(ws); + const controller = new AbortController(); + const pending = connection.sendCommand('navigate', { url: 'https://example.com' }, { + signal: controller.signal, + }); + const reason = new Error('caller stopped the browser run'); + controller.abort(reason); + + await assert.rejects(pending, (error) => error === reason); + assert.equal(connection.pending.size, 0); + assert.equal(ws.messages[0].command, 'navigate'); + assert.equal(ws.messages[1].command, 'cancelCommand'); + assert.equal(ws.messages[1].payload.commandId, ws.messages[0].id); +}); + +test('extension command results preserve false values', async () => { + const ws = new FakeWebSocket(); + const connection = connectionFor(ws); + const pending = connection.sendCommand('getPageInfo'); + const command = ws.messages[0]; + + ws.emit('message', JSON.stringify({ + type: 'result', + id: command.id, + ok: true, + result: false, + })); + + assert.equal(await pending, false); +}); + +test('extension command completion removes its abort listener', async () => { + const ws = new FakeWebSocket(); + const connection = connectionFor(ws); + const controller = new AbortController(); + const pending = connection.sendCommand('getPageInfo', {}, { signal: controller.signal }); + const command = ws.messages[0]; + + ws.emit('message', JSON.stringify({ + type: 'result', + id: command.id, + ok: true, + result: { url: 'https://example.com/' }, + })); + + assert.deepEqual(await pending, { url: 'https://example.com/' }); + controller.abort(); + assert.equal(ws.messages.length, 1); +}); + +test('extension URL validation requests are checked server-side and fail closed', async () => { + const ws = new FakeWebSocket(); + const checked = []; + const connection = new ExtensionBrowserConnection({ + registry: { + urlValidationLimit: 8, + touchPresence() {}, + unregisterConnection() {}, + async validateBrowserUrl(url) { + checked.push(url); + return { allowed: url === 'https://example.com/' }; + }, + }, + ws, + userId: 'user-1', + tokenId: 'token-1', + meta: {}, + timeoutMs: 5000, + heartbeatIntervalMs: 0, + heartbeatTimeoutMs: 0, + presenceTouchIntervalMs: 0, + }); + + ws.emit('message', JSON.stringify({ + type: 'urlValidationRequest', + version: 1, + id: 'allow-1', + url: 'https://example.com/', + })); + ws.emit('message', JSON.stringify({ + type: 'urlValidationRequest', + version: 999, + id: 'deny-1', + url: 'https://example.com/', + })); + await nextTurn(); + + assert.deepEqual(checked, ['https://example.com/']); + assert.deepEqual( + ws.messages.map(({ id, allowed }) => ({ id, allowed })).sort((a, b) => a.id.localeCompare(b.id)), + [ + { id: 'allow-1', allowed: true }, + { id: 'deny-1', allowed: false }, + ], + ); + connection.close(); +}); + +test('extension provider keeps AbortSignal out of WebSocket payloads', async () => { + const calls = []; + const registry = { + isConnected: () => true, + async dispatch(_userId, command, payload, options) { + calls.push({ command, payload, options }); + return { success: true }; + }, + }; + const provider = new ExtensionBrowserProvider({ registry, userId: 'user-1' }); + const controller = new AbortController(); + await provider.navigate('https://example.com/', { signal: controller.signal }); + + assert.equal(calls[0].payload.signal, undefined); + assert.equal(calls[0].options.signal, controller.signal); +}); + +test('extension provider validates screenshot bytes before creating an artifact', async () => { + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', + 'base64', + ); + let artifactOptions = null; + const provider = new ExtensionBrowserProvider({ + userId: 'user-1', + registry: { + async dispatch() { + return { screenshotData: png.toString('base64'), success: true }; + }, + }, + artifactStore: { + async createBufferArtifact(_userId, options) { + artifactOptions = options; + return { + artifactId: 'artifact-1', + storagePath: '/tmp/browser.png', + url: '/api/artifacts/artifact-1/content', + }; + }, + }, + }); + + const result = await provider.screenshot(); + + assert.equal(result.screenshotData, undefined); + assert.equal(result.screenshotPath, '/api/artifacts/artifact-1/content'); + assert.equal(artifactOptions.contentType, 'image/png'); + assert.deepEqual(artifactOptions.content, png); +}); + +test('extension provider rejects corrupt screenshot payloads', async () => { + const provider = new ExtensionBrowserProvider({ + userId: 'user-1', + registry: { + async dispatch() { + return { screenshotData: Buffer.from('not a png').toString('base64') }; + }, + }, + artifactStore: { + async createBufferArtifact() { + throw new Error('artifact creation should not run'); + }, + }, + }); + + await assert.rejects(provider.screenshot(), /not a valid PNG or JPEG/i); +}); + +test('revoking all browser tokens closes every live extension connection', () => { + const fakeDb = { + prepare() { + return { + run() {}, + get() { return null; }, + all() { return []; }, + }; + }, + }; + const registry = new BrowserExtensionRegistry({ db: fakeDb }); + const first = new FakeWebSocket(); + const second = new FakeWebSocket(); + registry.registerConnection({ + id: 'token-1', + user_id: 'user-1', + metadata: {}, + }, first); + registry.registerConnection({ + id: 'token-2', + user_id: 'user-1', + metadata: {}, + }, second); + + registry.revoke('user-1'); + + assert.equal(first.readyState, 3); + assert.equal(second.readyState, 3); + assert.equal(registry.isConnected('user-1', 'token-1'), false); + assert.equal(registry.isConnected('user-1', 'token-2'), false); +}); + +test('extension protocol cancellation settles a hanging Chrome callback', async () => { + const protocolUrl = pathToFileURL(path.resolve( + __dirname, + '../../../extensions/chrome-browser/protocol.mjs', + )).href; + const { COMMANDS, createBrowserProtocol } = await import(protocolUrl); + let notifyNavigateStarted; + const navigateStarted = new Promise((resolve) => { notifyNavigateStarted = resolve; }); + const noopEvent = { addListener() {} }; + const chromeApi = { + runtime: { lastError: null }, + tabs: { + onRemoved: noopEvent, + query(_options, callback) { + callback([{ id: 7, url: 'about:blank', title: '' }]); + }, + get(_tabId, callback) { + callback({ id: 7, url: 'about:blank', title: '' }); + }, + create(_options, callback) { + callback({ id: 7, url: 'about:blank', title: '' }); + }, + }, + debugger: { + onDetach: noopEvent, + attach(_debuggee, _version, callback) { callback(); }, + detach(_debuggee, callback) { callback(); }, + sendCommand(_debuggee, method, _params, callback) { + if (method === 'Page.navigate') { + notifyNavigateStarted(); + return; + } + callback({}); + }, + }, + cookies: { + getAll(_options, callback) { callback([]); }, + }, + }; + const protocol = createBrowserProtocol(chromeApi, { + validateUrl: async () => true, + }); + const controller = new AbortController(); + const pending = protocol.run(COMMANDS.NAVIGATE, { + url: 'https://example.com/', + screenshot: false, + }, { signal: controller.signal }); + await navigateStarted; + const reason = new Error('stop the hanging navigation'); + controller.abort(reason); + + await assert.rejects(pending, (error) => error === reason); +}); + +test('extension protocol intercepts redirects and subresources before network access', async () => { + const protocolUrl = pathToFileURL(path.resolve( + __dirname, + '../../../extensions/chrome-browser/protocol.mjs', + )).href; + const { + createBrowserProtocol, + normalizeNetworkValidationUrl, + } = await import(protocolUrl); + const commands = []; + let onDebuggerEvent = null; + const noopEvent = { addListener() {} }; + const chromeApi = { + runtime: { lastError: null }, + tabs: { + onRemoved: noopEvent, + query(_options, callback) { + callback([{ id: 7, url: 'about:blank', title: '' }]); + }, + get(_tabId, callback) { + callback({ id: 7, url: 'about:blank', title: '' }); + }, + create(_options, callback) { + callback({ id: 7, url: 'about:blank', title: '' }); + }, + }, + debugger: { + onDetach: noopEvent, + onEvent: { + addListener(listener) { onDebuggerEvent = listener; }, + }, + attach(_debuggee, _version, callback) { callback(); }, + detach(_debuggee, callback) { callback(); }, + sendCommand(debuggee, method, params, callback) { + commands.push({ debuggee, method, params }); + callback({}); + }, + }, + cookies: { + getAll(_options, callback) { callback([]); }, + }, + }; + const validated = []; + const protocol = createBrowserProtocol(chromeApi, { + async validateUrl(url) { + validated.push(url); + return { allowed: url === 'https://cdn.example.com/' }; + }, + }); + await protocol._test.attach(); + + onDebuggerEvent({ tabId: 7 }, 'Fetch.requestPaused', { + requestId: 'public-subresource', + request: { url: 'https://cdn.example.com/app.js?secret=not-forwarded' }, + resourceType: 'Script', + }); + onDebuggerEvent({ tabId: 7 }, 'Fetch.requestPaused', { + requestId: 'private-redirect', + request: { url: 'http://127.0.0.1/admin' }, + resourceType: 'Document', + redirectedRequestId: 'public-navigation', + }); + await nextTurn(); + + assert.equal(normalizeNetworkValidationUrl('wss://socket.example.com/live?token=x'), 'https://socket.example.com/'); + assert.equal(normalizeNetworkValidationUrl('http://[fe90::1]/'), null); + assert.deepEqual(validated, ['https://cdn.example.com/']); + assert.ok(commands.some(({ method }) => method === 'Fetch.enable')); + assert.ok(commands.some(({ method, params }) => ( + method === 'Fetch.continueRequest' && params.requestId === 'public-subresource' + ))); + assert.ok(commands.some(({ method, params }) => ( + method === 'Fetch.failRequest' + && params.requestId === 'private-redirect' + && params.errorReason === 'BlockedByClient' + ))); +}); + +test('extension protocol refuses page access when URL validation is unavailable', async () => { + const protocolUrl = pathToFileURL(path.resolve( + __dirname, + '../../../extensions/chrome-browser/protocol.mjs', + )).href; + const { COMMANDS, createBrowserProtocol } = await import(protocolUrl); + const noopEvent = { addListener() {} }; + let debuggerAttached = false; + const chromeApi = { + runtime: { lastError: null }, + tabs: { + onRemoved: noopEvent, + query(_options, callback) { + callback([{ id: 7, url: 'https://example.com/', title: '' }]); + }, + get(_tabId, callback) { + callback({ id: 7, url: 'https://example.com/', title: '' }); + }, + create(_options, callback) { callback({ id: 7, url: 'about:blank' }); }, + }, + debugger: { + onDetach: noopEvent, + onEvent: noopEvent, + attach(_debuggee, _version, callback) { + debuggerAttached = true; + callback(); + }, + detach(_debuggee, callback) { callback(); }, + sendCommand(_debuggee, _method, _params, callback) { callback({}); }, + }, + cookies: { getAll(_options, callback) { callback([]); } }, + }; + const protocol = createBrowserProtocol(chromeApi, { + async validateUrl() { + throw new Error('server disconnected'); + }, + }); + + await assert.rejects( + protocol.run(COMMANDS.SCREENSHOT), + /server disconnected/, + ); + assert.equal(debuggerAttached, false); +}); diff --git a/test/backend/unit/cli_executor.test.js b/test/backend/unit/cli_executor.test.js new file mode 100644 index 00000000..cbcc3417 --- /dev/null +++ b/test/backend/unit/cli_executor.test.js @@ -0,0 +1,33 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { CLIExecutor } = require('../../../server/services/cli/executor'); + +test('CLI execution never starts with an already-aborted signal', async () => { + const executor = new CLIExecutor(); + const controller = new AbortController(); + controller.abort('stopped'); + + const result = await executor.execute('echo should-not-run', { signal: controller.signal }); + + assert.equal(result.aborted, true); + assert.equal(result.pid, null); + assert.equal(executor.activeProcesses.size, 0); +}); + +test('CLI execution terminates the managed process when its run is aborted', async () => { + const executor = new CLIExecutor(); + const controller = new AbortController(); + const startedAt = Date.now(); + const running = executor.execute('sleep 20', { signal: controller.signal }); + setTimeout(() => controller.abort('stopped'), 50); + + const result = await running; + + assert.equal(result.killed, true); + assert.equal(result.aborted, true); + assert.ok(Date.now() - startedAt < 2000); + assert.equal(executor.activeProcesses.size, 0); +}); diff --git a/test/backend/unit/cloud_security.test.js b/test/backend/unit/cloud_security.test.js new file mode 100644 index 00000000..13390a7b --- /dev/null +++ b/test/backend/unit/cloud_security.test.js @@ -0,0 +1,78 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { + isPrivateHost, + validateAndroidIntentUrl, + validateCloudUrl, + validateCloudUrlWithDns, +} = require('../../../server/utils/cloud-security'); + +test('cloud URL validation uses an HTTP(S) allowlist', () => { + assert.equal(validateCloudUrl('https://example.com/path').allowed, true); + assert.equal(validateCloudUrl('http://example.com/path').allowed, true); + assert.equal(validateCloudUrl('ftp://example.com/file').allowed, false); + assert.equal(validateCloudUrl('mailto:user@example.com').allowed, false); + assert.equal(validateCloudUrl('file:///etc/passwd').allowed, false); +}); + +test('Android intent validation allows deep links but blocks local-content schemes', async () => { + assert.equal((await validateAndroidIntentUrl('geo:0,0?q=coffee')).allowed, true); + assert.equal((await validateAndroidIntentUrl('smsto:+1234567890')).allowed, true); + assert.equal((await validateAndroidIntentUrl('market://details?id=com.example')).allowed, true); + assert.equal((await validateAndroidIntentUrl('file:///data/local/tmp/secret')).allowed, false); + assert.equal((await validateAndroidIntentUrl('content://settings/system')).allowed, false); +}); + +test('private host validation covers reserved and special-use IP ranges', () => { + for (const address of [ + '127.0.0.1', + '169.254.169.254', + '192.168.1.1', + '198.18.0.1', + '203.0.113.4', + '::1', + 'fd00::1', + 'fe90::1', + 'fec0::1', + 'ff02::1', + '100::1', + '2001:db8::1', + ]) { + assert.equal(isPrivateHost(address), true, address); + } + assert.equal(isPrivateHost('8.8.8.8'), false); +}); + +test('DNS validation blocks hostnames that resolve to internal addresses', async () => { + const privateResult = await validateCloudUrlWithDns('https://public-name.example', { + lookup: async () => [{ address: '10.0.0.8', family: 4 }], + }); + const mixedResult = await validateCloudUrlWithDns('https://rebinding.example', { + lookup: async () => [ + { address: '93.184.216.34', family: 4 }, + { address: '127.0.0.1', family: 4 }, + ], + }); + const publicResult = await validateCloudUrlWithDns('https://example.com', { + lookup: async () => [{ address: '93.184.216.34', family: 4 }], + }); + + assert.equal(privateResult.allowed, false); + assert.equal(mixedResult.allowed, false); + assert.equal(publicResult.allowed, true); +}); + +test('DNS validation stops promptly when cancelled', async () => { + const controller = new AbortController(); + const validation = validateCloudUrlWithDns('https://slow.example', { + lookup: () => new Promise(() => {}), + signal: controller.signal, + timeoutMs: 5000, + }); + controller.abort(); + + await assert.rejects(validation, (error) => error.name === 'AbortError'); +}); diff --git a/test/backend/unit/database_bootstrap_legacy_indexes.test.js b/test/backend/unit/database_bootstrap_legacy_indexes.test.js index 3993abdd..b0f271be 100644 --- a/test/backend/unit/database_bootstrap_legacy_indexes.test.js +++ b/test/backend/unit/database_bootstrap_legacy_indexes.test.js @@ -175,3 +175,24 @@ test('capture cleanup preserves legacy transcription choices for voice features' ); db.close(); }); + +test('memory embedding index migration is idempotent and keeps the current lookup shape', () => { + const db = new Sqlite(':memory:'); + const { migrateMemoryEmbeddingIndex } = require('../../../lib/schema_migrations'); + + migrateMemoryEmbeddingIndex(db); + assert.doesNotThrow(() => migrateMemoryEmbeddingIndex(db)); + + const columns = db.prepare( + 'PRAGMA index_info(idx_memory_embedding_bands_lookup)', + ).all().map((column) => column.name); + assert.deepEqual(columns, [ + 'user_id', + 'agent_id', + 'dimension', + 'index_version', + 'band_index', + 'band_value', + ]); + db.close(); +}); diff --git a/test/backend/unit/desktop_companion_connection.test.js b/test/backend/unit/desktop_companion_connection.test.js new file mode 100644 index 00000000..514a97d7 --- /dev/null +++ b/test/backend/unit/desktop_companion_connection.test.js @@ -0,0 +1,140 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { EventEmitter } = require('node:events'); +const { test } = require('node:test'); + +const { + DesktopCompanionConnection, +} = require('../../../server/services/desktop/registry'); +const { DesktopProvider } = require('../../../server/services/desktop/provider'); + +class FakeWebSocket extends EventEmitter { + constructor() { + super(); + this.readyState = 1; + this.messages = []; + } + + send(raw) { + this.messages.push(JSON.parse(raw)); + } + + close() { + this.readyState = 3; + this.emit('close'); + } + + ping() {} +} + +function createConnection() { + const ws = new FakeWebSocket(); + const registry = { + touchPresence() {}, + unregisterConnection() {}, + }; + return { + ws, + connection: new DesktopCompanionConnection({ + registry, + ws, + userId: 1, + sessionId: 'session', + deviceId: 'device', + recordId: 'record', + meta: {}, + timeoutMs: 5000, + heartbeatIntervalMs: 0, + heartbeatTimeoutMs: 0, + presenceTouchIntervalMs: 0, + }), + }; +} + +test('aborting a desktop command tells the companion to cancel the real work', async () => { + const { connection, ws } = createConnection(); + const controller = new AbortController(); + const pending = connection.sendCommand( + 'executeCommand', + { command: 'long-running' }, + { signal: controller.signal }, + ); + const reason = new Error('caller cancelled desktop work'); + controller.abort(reason); + + await assert.rejects(pending, (error) => error === reason); + assert.equal(connection.pending.size, 0); + assert.equal(ws.messages.length, 2); + assert.equal(ws.messages[1].command, 'cancelCommand'); + assert.equal(ws.messages[1].payload.commandId, ws.messages[0].id); +}); + +test('desktop command results preserve false payloads', async () => { + const { connection, ws } = createConnection(); + const pending = connection.sendCommand('getStatus'); + const command = ws.messages[0]; + ws.emit('message', JSON.stringify({ + type: 'result', + id: command.id, + ok: true, + payload: false, + })); + + assert.equal(await pending, false); +}); + +test('a completed desktop command removes its abort listener', async () => { + const { connection, ws } = createConnection(); + const controller = new AbortController(); + const pending = connection.sendCommand('observe', {}, { signal: controller.signal }); + const command = ws.messages[0]; + ws.emit('message', JSON.stringify({ + type: 'result', + id: command.id, + ok: true, + payload: { success: true }, + })); + + assert.deepEqual(await pending, { success: true }); + controller.abort(); + assert.equal(ws.messages.length, 1); + assert.equal(connection.pending.size, 0); +}); + +test('desktop provider derives screenshot type from bytes instead of companion metadata', async () => { + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', + 'base64', + ); + let artifactOptions = null; + const provider = new DesktopProvider({ + userId: 'user-1', + registry: { + async dispatch() { + return { + success: true, + contentType: 'image/jpeg', + screenshotBase64: png.toString('base64'), + }; + }, + }, + artifactStore: { + async createBufferArtifact(_userId, options) { + artifactOptions = options; + return { + artifactId: 'artifact-1', + storagePath: '/tmp/desktop.png', + url: '/api/artifacts/artifact-1/content', + }; + }, + }, + }); + + const result = await provider.screenshot(); + + assert.equal(result.screenshotBase64, undefined); + assert.equal(artifactOptions.contentType, 'image/png'); + assert.equal(artifactOptions.extension, 'png'); + assert.deepEqual(artifactOptions.content, png); +}); diff --git a/test/backend/unit/engine_model_response.test.js b/test/backend/unit/engine_model_response.test.js index 2907f10f..ffd97644 100644 --- a/test/backend/unit/engine_model_response.test.js +++ b/test/backend/unit/engine_model_response.test.js @@ -256,3 +256,34 @@ test('requestModelResponse times out a stalled stream without replaying partial assert.equal(calls, 1); }); + +test('requestModelResponse returns promptly when the parent aborts even if the provider ignores signals', async () => { + const engine = new AgentEngine(null); + const controller = new AbortController(); + const request = engine.requestModelResponse({ + provider: { + async chat() { + return new Promise(() => {}); + }, + }, + providerName: 'test', + model: 'test-model', + messages: [{ role: 'user', content: 'Run the task.' }], + tools: [], + options: { + stream: false, + userId: 1, + signal: controller.signal, + modelCallTimeoutMs: 10_000, + retry: { maxAttempts: 1 }, + }, + runId: 'abort-ignoring-provider-run', + iteration: 1, + }); + + controller.abort('run stopped'); + await assert.rejects( + request, + (error) => error.name === 'AbortError' && error.code === 'ABORT_ERR', + ); +}); diff --git a/test/backend/unit/github_copilot_refresh.test.js b/test/backend/unit/github_copilot_refresh.test.js new file mode 100644 index 00000000..18c37d29 --- /dev/null +++ b/test/backend/unit/github_copilot_refresh.test.js @@ -0,0 +1,50 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, test } = require('node:test'); + +const { GithubCopilotProvider } = require('../../../server/services/ai/providers/githubCopilot'); + +const originalFetch = global.fetch; + +afterEach(() => { + global.fetch = originalFetch; +}); + +test('Copilot token refresh is shared without letting one caller cancel everyone', async () => { + let resolveFetch; + let capturedSignal = null; + let calls = 0; + global.fetch = (_url, options) => { + calls += 1; + capturedSignal = options.signal; + return new Promise((resolve) => { + resolveFetch = resolve; + }); + }; + const provider = new GithubCopilotProvider({ apiKey: 'github-access-token' }); + const controller = new AbortController(); + const cancelledWaiter = provider._refreshCopilotToken(controller.signal); + const survivingWaiter = provider._refreshCopilotToken(); + await new Promise((resolve) => setImmediate(resolve)); + + const reason = new Error('first run stopped'); + controller.abort(reason); + await assert.rejects(cancelledWaiter, (error) => error === reason); + assert.equal(capturedSignal.aborted, false); + + resolveFetch({ + ok: true, + status: 200, + headers: { get: () => null }, + text: async () => JSON.stringify({ + token: 'copilot-session-token', + expires_at: Math.floor(Date.now() / 1000) + 1800, + }), + }); + await survivingWaiter; + + assert.equal(calls, 1); + assert.equal(provider.copilotToken, 'copilot-session-token'); + assert.equal(provider.client.apiKey, 'copilot-session-token'); +}); diff --git a/test/backend/unit/http_request_signal.test.js b/test/backend/unit/http_request_signal.test.js new file mode 100644 index 00000000..3fa44845 --- /dev/null +++ b/test/backend/unit/http_request_signal.test.js @@ -0,0 +1,35 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { EventEmitter } = require('node:events'); +const { test } = require('node:test'); + +const { attachRequestSignal } = require('../../../server/http/middleware'); + +test('HTTP request signal aborts when the client disconnects', () => { + const req = new EventEmitter(); + const res = new EventEmitter(); + res.writableEnded = false; + let nextCalled = false; + + attachRequestSignal(req, res, () => { nextCalled = true; }); + req.emit('aborted'); + + assert.equal(nextCalled, true); + assert.equal(req.signal.aborted, true); + assert.equal(req.signal.reason?.code, 'ABORT_ERR'); +}); + +test('HTTP request signal listeners are removed after a normal response', () => { + const req = new EventEmitter(); + const res = new EventEmitter(); + res.writableEnded = true; + + attachRequestSignal(req, res, () => {}); + res.emit('finish'); + res.emit('close'); + + assert.equal(req.signal.aborted, false); + assert.equal(req.listenerCount('aborted'), 0); + assert.equal(res.listenerCount('close'), 0); +}); diff --git a/test/backend/unit/http_request_tool.test.js b/test/backend/unit/http_request_tool.test.js new file mode 100644 index 00000000..b9542332 --- /dev/null +++ b/test/backend/unit/http_request_tool.test.js @@ -0,0 +1,160 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const http = require('node:http'); +const { afterEach, test } = require('node:test'); + +const { + executeHttpRequest, + resolveHttpTarget, +} = require('../../../server/services/ai/integrated_tools/http_request'); +const { executeSafeHttpRequest } = require('../../../server/services/network/safe_request'); + +const servers = []; + +async function listen(handler) { + const server = http.createServer(handler); + servers.push(server); + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + return server.address().port; +} + +const localLookup = async () => [{ address: '127.0.0.1', family: 4 }]; + +afterEach(async () => { + await Promise.all(servers.splice(0).map((server) => new Promise((resolve) => { + server.closeAllConnections?.(); + server.close(resolve); + }))); +}); + +test('HTTP tool rejects DNS names resolving to private addresses by default', async () => { + await assert.rejects( + resolveHttpTarget('https://apparently-public.example/resource', { + lookup: localLookup, + }), + /resolves to a private, loopback, or reserved address/, + ); +}); + +test('HTTP tool validates redirects and strips credentials across origins', async () => { + let firstAuthorization = null; + let redirectedAuthorization = null; + let redirectedMethod = null; + let redirectedContentType = null; + const secondPort = await listen((request, response) => { + redirectedAuthorization = request.headers.authorization || null; + redirectedMethod = request.method; + redirectedContentType = request.headers['content-type'] || null; + response.writeHead(200, { + 'content-type': 'text/plain', + 'set-cookie': 'session=secret', + }); + response.end('redirect complete'); + }); + const firstPort = await listen((request, response) => { + firstAuthorization = request.headers.authorization || null; + response.writeHead(302, { + location: `http://second.example:${secondPort}/finish`, + }); + response.end(); + }); + + const result = await executeHttpRequest({ + url: `http://first.example:${firstPort}/start`, + method: 'POST', + headers: { Authorization: 'Bearer private-token' }, + body: '{"run":true}', + }, { + allowPrivate: true, + lookup: localLookup, + }); + + assert.equal(firstAuthorization, 'Bearer private-token'); + assert.equal(redirectedAuthorization, null); + assert.equal(redirectedMethod, 'GET'); + assert.equal(redirectedContentType, null); + assert.equal(result.status, 200); + assert.equal(result.body, 'redirect complete'); + assert.equal(result.headers['set-cookie'], undefined); + assert.deepEqual(result.redirects, [`http://second.example:${secondPort}/finish`]); +}); + +test('HTTP tool cancels a live request with the caller reason', async () => { + let markRequestArrived; + const requestArrived = new Promise((resolve) => { + markRequestArrived = resolve; + }); + const port = await listen((_request, _response) => markRequestArrived()); + const controller = new AbortController(); + const pending = executeHttpRequest({ + url: `http://cancel.example:${port}/wait`, + }, { + allowPrivate: true, + lookup: localLookup, + signal: controller.signal, + }); + await requestArrived; + + const reason = new Error('agent run stopped'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); +}); + +test('HTTP tool bounds and actively truncates oversized response bodies', async () => { + const port = await listen((_request, response) => { + response.writeHead(200, { 'content-type': 'text/plain' }); + response.end('1234567890abcdef'); + }); + + const result = await executeHttpRequest({ + url: `http://large.example:${port}/large`, + }, { + allowPrivate: true, + lookup: localLookup, + maxResponseBytes: 8, + }); + + assert.equal(result.truncated, true); + assert.equal(result.body, '12345678\n...[truncated at response safety limit]'); +}); + +test('HTTP tool timeout is distinct from caller cancellation', async () => { + let markRequestArrived; + const requestArrived = new Promise((resolve) => { + markRequestArrived = resolve; + }); + const port = await listen((_request, _response) => markRequestArrived()); + + const pending = executeHttpRequest({ + url: `http://timeout.example:${port}/wait`, + timeout_ms: 100, + }, { + allowPrivate: true, + lookup: localLookup, + }); + await requestArrived; + await assert.rejects( + pending, + (error) => error.code === 'HTTP_REQUEST_TIMEOUT', + ); +}); + +test('shared safe requester can preserve binary response bodies', async () => { + const payload = Buffer.from([0, 255, 16, 128, 42]); + const port = await listen((_request, response) => { + response.writeHead(200, { 'content-type': 'application/octet-stream' }); + response.end(payload); + }); + + const result = await executeSafeHttpRequest({ + url: `http://binary.example:${port}/asset`, + }, { + allowPrivate: true, + lookup: localLookup, + responseType: 'buffer', + }); + + assert.ok(Buffer.isBuffer(result.body)); + assert.deepEqual(result.body, payload); +}); diff --git a/test/backend/unit/image_payload.test.js b/test/backend/unit/image_payload.test.js new file mode 100644 index 00000000..7a34811d --- /dev/null +++ b/test/backend/unit/image_payload.test.js @@ -0,0 +1,44 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { + decodeBase64Image, + validateImageBuffer, +} = require('../../../server/utils/image_payload'); + +const ONE_PIXEL_PNG = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', + 'base64', +); + +test('decodes a bounded PNG data URL and verifies its real byte type', () => { + const image = decodeBase64Image(`data:image/png;base64,${ONE_PIXEL_PNG.toString('base64')}`); + + assert.equal(image.contentType, 'image/png'); + assert.equal(image.extension, 'png'); + assert.equal(image.width, 1); + assert.equal(image.height, 1); + assert.deepEqual(image.buffer, ONE_PIXEL_PNG); +}); + +test('rejects malformed base64 and MIME spoofing', () => { + assert.throws(() => decodeBase64Image('%%%not-base64%%%'), /base64 payload is malformed/i); + assert.throws( + () => decodeBase64Image(`data:image/jpeg;base64,${ONE_PIXEL_PNG.toString('base64')}`), + /type does not match/i, + ); +}); + +test('rejects oversized and non-image screenshot bytes', () => { + const oversized = Buffer.concat([ONE_PIXEL_PNG, Buffer.alloc(2048)]); + assert.throws( + () => validateImageBuffer(oversized, { maxBytes: 1024 }), + (error) => error.code === 'IMAGE_PAYLOAD_TOO_LARGE', + ); + assert.throws( + () => validateImageBuffer(Buffer.from('not an image')), + /not a valid PNG or JPEG/i, + ); +}); diff --git a/test/backend/unit/integration_http.test.js b/test/backend/unit/integration_http.test.js new file mode 100644 index 00000000..700bb677 --- /dev/null +++ b/test/backend/unit/integration_http.test.js @@ -0,0 +1,228 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, test } = require('node:test'); + +const { + fetchResponseText, + waitForBoundedResult, +} = require('../../../server/services/integrations/http'); +const { + fetchJson, +} = require('../../../server/services/integrations/oauth_provider'); + +const originalFetch = global.fetch; + +afterEach(() => { + global.fetch = originalFetch; +}); + +test('integration HTTP requests forward caller cancellation and preserve its reason', async () => { + const controller = new AbortController(); + let capturedSignal = null; + global.fetch = (_url, options) => { + capturedSignal = options.signal; + return new Promise((resolve, reject) => { + options.signal.addEventListener('abort', () => reject(options.signal.reason), { + once: true, + }); + }); + }; + + const pending = fetchJson( + 'https://integration.example.test/resource', + { signal: controller.signal }, + { serviceName: 'Example' }, + ); + await new Promise((resolve) => setImmediate(resolve)); + assert.ok(capturedSignal); + assert.notEqual(capturedSignal, controller.signal); + + const reason = new Error('run stopped'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.equal(capturedSignal.aborted, true); +}); + +test('integration HTTP timeout settles even when fetch ignores AbortSignal', async () => { + global.fetch = () => new Promise(() => {}); + + await assert.rejects( + fetchResponseText( + 'https://integration.example.test/resource', + { timeoutMs: 10 }, + { serviceName: 'Example' }, + ), + (error) => { + assert.equal(error.code, 'INTEGRATION_HTTP_TIMEOUT'); + assert.match(error.message, /Example request timed out after 10ms/); + return true; + }, + ); +}); + +test('integration HTTP cancellation closes a non-cooperative response reader', async () => { + const controller = new AbortController(); + const reason = new Error('response no longer needed'); + let cancelCalled = false; + global.fetch = async () => ({ + headers: { get: () => null }, + body: { + getReader() { + return { + read: () => new Promise(() => {}), + cancel: async () => { + cancelCalled = true; + }, + releaseLock() {}, + }; + }, + }, + }); + + const pending = fetchResponseText( + 'https://integration.example.test/stream', + { signal: controller.signal }, + { serviceName: 'Example' }, + ); + await new Promise((resolve) => setImmediate(resolve)); + controller.abort(reason); + + await assert.rejects(pending, (error) => error === reason); + assert.equal(cancelCalled, true); +}); + +test('bounded integration waits settle even when a third-party SDK never does', async () => { + await assert.rejects( + waitForBoundedResult(new Promise(() => {}), { + timeoutMs: 10, + serviceName: 'SDK operation', + }), + (error) => { + assert.equal(error.code, 'INTEGRATION_HTTP_TIMEOUT'); + assert.match(error.message, /SDK operation request timed out after 10ms/); + return true; + }, + ); +}); + +test('integration HTTP responses are bounded before JSON parsing', async () => { + global.fetch = async () => ({ + headers: { get: () => null }, + text: async () => 'response-body-too-large', + }); + + await assert.rejects( + fetchResponseText( + 'https://integration.example.test/resource', + { maxResponseBytes: 8 }, + { serviceName: 'Example' }, + ), + (error) => { + assert.equal(error.code, 'INTEGRATION_RESPONSE_TOO_LARGE'); + return true; + }, + ); +}); + +test('fetchJson keeps Slack-style ok=false responses as failures', async () => { + global.fetch = async () => ({ + ok: true, + status: 200, + statusText: 'OK', + headers: { get: () => null }, + text: async () => JSON.stringify({ ok: false, error: 'invalid_auth' }), + }); + + await assert.rejects( + fetchJson( + 'https://slack.example.test/api', + {}, + { serviceName: 'Slack' }, + ), + /Slack request failed: invalid_auth/, + ); +}); + +test('fetchJson retries a throttled read after Retry-After and then succeeds', async () => { + let calls = 0; + global.fetch = async () => { + calls += 1; + if (calls === 1) { + return { + ok: false, + status: 429, + statusText: 'Too Many Requests', + headers: { get: (name) => name.toLowerCase() === 'retry-after' ? '0' : null }, + text: async () => JSON.stringify({ error: 'rate_limited' }), + }; + } + return { + ok: true, + status: 200, + statusText: 'OK', + headers: { get: () => null }, + text: async () => JSON.stringify({ value: 'ready' }), + }; + }; + + assert.deepEqual( + await fetchJson( + 'https://integration.example.test/resource', + {}, + { serviceName: 'Example' }, + ), + { value: 'ready' }, + ); + assert.equal(calls, 2); +}); + +test('fetchJson never automatically retries a failed write', async () => { + let calls = 0; + global.fetch = async () => { + calls += 1; + return { + ok: false, + status: 503, + statusText: 'Service Unavailable', + headers: { get: () => null }, + text: async () => JSON.stringify({ error: 'unavailable' }), + }; + }; + + await assert.rejects( + fetchJson( + 'https://integration.example.test/resource', + { method: 'POST', json: { value: 1 } }, + { serviceName: 'Example' }, + ), + /unavailable/, + ); + assert.equal(calls, 1); +}); + +test('fetchJson aborts during a server-requested retry delay', async () => { + let calls = 0; + global.fetch = async () => { + calls += 1; + return { + ok: false, + status: 429, + statusText: 'Too Many Requests', + headers: { get: (name) => name.toLowerCase() === 'retry-after' ? '5' : null }, + text: async () => JSON.stringify({ error: 'rate_limited' }), + }; + }; + const controller = new AbortController(); + const pending = fetchJson( + 'https://integration.example.test/resource', + { signal: controller.signal }, + { serviceName: 'Example' }, + ); + await new Promise((resolve) => setImmediate(resolve)); + + const reason = new Error('tool stopped'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.equal(calls, 1); +}); diff --git a/test/backend/unit/integration_oauth_state.test.js b/test/backend/unit/integration_oauth_state.test.js new file mode 100644 index 00000000..9c34199d --- /dev/null +++ b/test/backend/unit/integration_oauth_state.test.js @@ -0,0 +1,77 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { + createTestRuntime, + createTestUser, + teardownTestRuntime, +} = require('../../helpers/db'); + +function deferred() { + let resolve; + const promise = new Promise((done) => { + resolve = done; + }); + return { promise, resolve }; +} + +test('official OAuth state is claimed before token exchange and cannot race', async () => { + const ctx = createTestRuntime(); + try { + const user = await createTestUser(ctx.db, { username: 'oauth_state_user' }); + const { IntegrationManager } = require('../../../server/services/integrations/manager'); + const exchangeStarted = deferred(); + const releaseExchange = deferred(); + let exchangeCount = 0; + let issuedState = null; + const provider = { + key: 'test_oauth', + label: 'Test OAuth', + requiresRefreshToken: false, + getApp: (appKey) => appKey === 'mail' ? { id: 'mail' } : null, + getEnvStatus: () => ({ configured: true }), + beginOAuth({ state }) { + issuedState = state; + return { url: `https://provider.example.test/authorize?state=${state}` }; + }, + async finishOAuth() { + exchangeCount += 1; + exchangeStarted.resolve(); + await releaseExchange.promise; + return { + accountEmail: 'person@example.test', + credentials: { access_token: 'access-token' }, + scopes: ['mail.read'], + }; + }, + }; + const manager = new IntegrationManager(); + manager.registry = { + get: (key) => key === provider.key ? provider : null, + list: () => [provider], + }; + + await manager.beginOAuth(user.userId, provider.key, { appKey: 'mail' }); + const first = manager.finishOAuth(issuedState, 'authorization-code'); + await exchangeStarted.promise; + + await assert.rejects( + manager.finishOAuth(issuedState, 'replayed-code'), + /missing or expired/, + ); + assert.equal(exchangeCount, 1); + + releaseExchange.resolve(); + const completed = await first; + assert.equal(completed.accountEmail, 'person@example.test'); + assert.equal( + ctx.db.prepare('SELECT COUNT(*) AS count FROM integration_oauth_states WHERE state = ?') + .get(issuedState).count, + 0, + ); + } finally { + teardownTestRuntime(ctx); + } +}); diff --git a/test/backend/unit/memory_embeddings.test.js b/test/backend/unit/memory_embeddings.test.js new file mode 100644 index 00000000..2a716af4 --- /dev/null +++ b/test/backend/unit/memory_embeddings.test.js @@ -0,0 +1,118 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { EventEmitter } = require('node:events'); +const https = require('node:https'); +const { afterEach, test } = require('node:test'); + +const originalRequest = https.request; +const originalGoogleKey = process.env.GOOGLE_AI_KEY; +const originalOpenAIKey = process.env.OPENAI_API_KEY; + +function restoreEnv(name, value) { + if (value == null) delete process.env[name]; + else process.env[name] = value; +} + +function installRequestMock(handler) { + https.request = (options, callback) => { + const request = new EventEmitter(); + request.destroyed = false; + request.destroy = (error) => { + request.destroyed = true; + if (error) setImmediate(() => request.emit('error', error)); + }; + request.end = (body) => handler({ body, callback, options, request }); + return request; + }; +} + +function respondJson(callback, payload, options = {}) { + const response = new EventEmitter(); + response.statusCode = options.statusCode || 200; + response.headers = options.headers || {}; + response.destroyed = false; + response.destroy = () => { + response.destroyed = true; + }; + callback(response); + if (!response.destroyed) { + const body = Buffer.from(JSON.stringify(payload)); + response.emit('data', body); + response.emit('end'); + } + return response; +} + +afterEach(() => { + https.request = originalRequest; + restoreEnv('GOOGLE_AI_KEY', originalGoogleKey); + restoreEnv('OPENAI_API_KEY', originalOpenAIKey); +}); + +test('Google memory embeddings use the current model without putting the API key in the URL', async () => { + process.env.GOOGLE_AI_KEY = 'google-test-key'; + delete process.env.OPENAI_API_KEY; + let captured = null; + installRequestMock(({ body, callback, options }) => { + captured = { body: JSON.parse(body), options }; + setImmediate(() => respondJson(callback, { + embedding: { values: Array(768).fill(0.25) }, + })); + }); + + const { + GOOGLE_MODEL, + getEmbeddingWithMetadata, + } = require('../../../server/services/memory/embeddings'); + const result = await getEmbeddingWithMetadata('find this memory', 'google', { + inputType: 'query', + }); + + assert.equal(GOOGLE_MODEL, 'gemini-embedding-2'); + assert.equal(result.model, GOOGLE_MODEL); + assert.equal(result.dimensions, 768); + assert.equal(captured.options.path, `/v1beta/models/${GOOGLE_MODEL}:embedContent`); + assert.equal(captured.options.path.includes('google-test-key'), false); + assert.equal(captured.options.headers['x-goog-api-key'], 'google-test-key'); + assert.equal(captured.body.output_dimensionality, 768); + assert.match(captured.body.content.parts[0].text, /^task: search result \| query:/); +}); + +test('embedding requests preserve caller cancellation and tear down the socket', async () => { + delete process.env.GOOGLE_AI_KEY; + process.env.OPENAI_API_KEY = 'openai-test-key'; + let request = null; + installRequestMock((context) => { + request = context.request; + }); + + const { getEmbeddingWithMetadata } = require('../../../server/services/memory/embeddings'); + const controller = new AbortController(); + const pending = getEmbeddingWithMetadata('cancel me', 'openai', { + signal: controller.signal, + }); + await new Promise((resolve) => setImmediate(resolve)); + + const reason = new Error('ingestion stopped'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.equal(request.destroyed, true); +}); + +test('embedding responses reject oversized bodies without buffering them', async () => { + delete process.env.GOOGLE_AI_KEY; + process.env.OPENAI_API_KEY = 'openai-test-key'; + let response = null; + installRequestMock(({ callback }) => { + setImmediate(() => { + response = respondJson(callback, {}, { + headers: { 'content-length': String(3 * 1024 * 1024) }, + }); + }); + }); + + const { getEmbeddingWithMetadata } = require('../../../server/services/memory/embeddings'); + assert.equal(await getEmbeddingWithMetadata('too large', 'openai'), null); + assert.equal(response.destroyed, true); +}); diff --git a/test/backend/unit/memory_ingestion_lifecycle.test.js b/test/backend/unit/memory_ingestion_lifecycle.test.js index 364c3f04..347f056b 100644 --- a/test/backend/unit/memory_ingestion_lifecycle.test.js +++ b/test/backend/unit/memory_ingestion_lifecycle.test.js @@ -204,3 +204,84 @@ test('memory ingestion rejects invalid polling intervals', () => { /greater than or equal to 1000/, ); }); + +test('memory ingestion stop aborts a cooperative provider without recording a failure', async () => { + const timerHarness = createTimerHarness(); + const collectorStarted = deferred(); + const memoryManager = createMemoryManager(); + const connection = { + id: 1, + user_id: 10, + agent_id: 'agent-10', + provider_key: 'google_workspace', + app_key: 'gmail', + account_email: 'user@example.com', + status: 'connected', + }; + const service = new MemoryIngestionService({ + memoryManager, + integrationManager: { + getProvider() { + return { + collectMemoryDocuments({ signal }) { + collectorStarted.resolve(signal); + return new Promise((_, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }); + }); + }, + }; + }, + }, + intervalMs: 5000, + database: { + prepare() { + return { all: () => [connection] }; + }, + }, + ...timerHarness, + }); + + service.start(); + const signal = await collectorStarted.promise; + const status = await service.stop(); + + assert.equal(signal.aborted, true); + assert.equal(status.state, 'stopped'); + assert.equal(status.lastError, null); + assert.equal(memoryManager.jobs.some((job) => job.status === 'failed'), false); +}); + +test('caller cancellation marks an in-progress ingestion job as cancelled', async () => { + const saveStarted = deferred(); + const memoryManager = { + ...createMemoryManager(), + upsertIngestionDocument() { + return 'document-1'; + }, + saveMemory(_userId, _content, _category, _importance, { signal }) { + saveStarted.resolve(); + return new Promise((_, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }); + }); + }, + }; + const service = new MemoryIngestionService({ memoryManager }); + const controller = new AbortController(); + const pending = service.ingestDocuments(null, [{ + externalObjectId: 'message-1', + sourceType: 'email', + title: 'Subject', + content: 'A durable source document with enough content to create a chunk.', + }], { + sourceType: 'email', + signal: controller.signal, + }); + await saveStarted.promise; + + const reason = new Error('request disconnected'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.equal(memoryManager.jobs.at(-1).status, 'cancelled'); + assert.equal(memoryManager.jobs.at(-1).error, null); + assert.equal(memoryManager.jobs.at(-1).nextSyncAt, null); +}); diff --git a/test/backend/unit/messaging_automation.test.js b/test/backend/unit/messaging_automation.test.js index 2c5f518f..0f10f7fb 100644 --- a/test/backend/unit/messaging_automation.test.js +++ b/test/backend/unit/messaging_automation.test.js @@ -305,4 +305,67 @@ describe('messaging automation queue', () => { assert.ok(errorEvent.payload.runId); assert.deepEqual(Object.keys(app.locals.userQueues), []); }); + + test('automation shutdown aborts an active run and drops queued work without a false failure', async () => { + const manager = new MessagingManagerStub(); + const io = createIoRecorder(); + const app = { locals: {} }; + const { accessPolicyKey } = require('../../../server/services/messaging/access_policy'); + ctx.db.prepare( + `INSERT INTO agent_settings (user_id, agent_id, key, value) + VALUES (?, ?, ?, ?)` + ).run( + user.userId, + mainAgentId, + accessPolicyKey('telegram'), + JSON.stringify({ + directPolicy: 'open', + sharedPolicy: 'disabled', + requireMentionInShared: true, + directRules: [], + sharedSpaceRules: [], + sharedActorRules: [], + }), + ); + let runCalls = 0; + let runSignal = null; + const runtime = automation.registerMessagingAutomation({ + app, + io, + messagingManager: manager, + agentEngine: { + run(_userId, _prompt, options) { + runCalls += 1; + runSignal = options.signal; + return new Promise((resolve, reject) => { + runSignal.addEventListener('abort', () => reject(runSignal.reason), { once: true }); + }); + }, + }, + }); + + const active = manager.handler( + user.userId, + createMessage(mainAgentId, 'first'), + ); + await waitForTurn(); + const queued = await manager.handler( + user.userId, + createMessage(mainAgentId, 'second'), + ); + assert.deepEqual(queued, { queued: true }); + assert.equal(runCalls, 1); + assert.ok(runSignal); + + const firstShutdown = runtime.shutdown(); + const secondShutdown = runtime.shutdown(); + assert.equal(firstShutdown, secondShutdown); + const [status] = await Promise.all([firstShutdown, active]); + + assert.equal(status.state, 'stopped'); + assert.equal(runSignal.aborted, true); + assert.equal(runCalls, 1); + assert.deepEqual(Object.keys(app.locals.userQueues), []); + assert.equal(io.events.some((entry) => entry.event === 'messaging:error'), false); + }); }); diff --git a/test/backend/unit/messaging_inbound_durability.test.js b/test/backend/unit/messaging_inbound_durability.test.js new file mode 100644 index 00000000..897921a2 --- /dev/null +++ b/test/backend/unit/messaging_inbound_durability.test.js @@ -0,0 +1,132 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, beforeEach, test } = require('node:test'); + +const { + createTestRuntime, + createTestUser, + teardownTestRuntime, +} = require('../../helpers/db'); + +let ctx; +let user; +let MessagingManager; + +function createIo() { + return { + to() { + return { emit() {} }; + }, + }; +} + +function inboundMessage(overrides = {}) { + return { + chatId: 'chat-1', + messageId: 'platform-message-1', + sender: 'sender-1', + senderName: 'Sender', + content: 'Please finish this task.', + isGroup: false, + timestamp: new Date().toISOString(), + ...overrides, + }; +} + +beforeEach(async () => { + ctx = createTestRuntime(); + user = await createTestUser(ctx.db); + ({ MessagingManager } = require('../../../server/services/messaging/manager')); +}); + +afterEach(() => { + teardownTestRuntime(ctx); +}); + +test('persists an inbound job before dispatch and deduplicates platform retries', async () => { + const manager = new MessagingManager(createIo()); + + await manager.ingestMessage(user.userId, 'telegram', inboundMessage()); + await manager.ingestMessage(user.userId, 'telegram', inboundMessage()); + + const messages = ctx.db.prepare( + "SELECT COUNT(*) AS count FROM messages WHERE role = 'user' AND platform_msg_id = ?", + ).get('platform-message-1'); + const jobs = ctx.db.prepare( + 'SELECT status, attempts FROM messaging_inbound_jobs', + ).all(); + assert.equal(messages.count, 1); + assert.deepEqual(jobs, [{ status: 'pending', attempts: 0 }]); +}); + +test('recovers a pending inbound job once its platform and handler are ready', async () => { + const firstManager = new MessagingManager(createIo()); + const stored = await firstManager.ingestMessage( + user.userId, + 'telegram', + inboundMessage(), + ); + const secondManager = new MessagingManager(createIo()); + const calls = []; + secondManager.registerHandler(async (userId, message) => { + calls.push({ userId, message }); + return { runId: 'recovered-run', result: { status: 'completed' }, error: null }; + }); + const agentId = secondManager._agentId(user.userId, {}); + secondManager.platforms.set( + secondManager._key(user.userId, agentId, 'telegram'), + { getStatus: () => 'connected' }, + ); + + const recovery = await secondManager.recoverPendingInbound(); + + assert.deepEqual(recovery, { recovered: 1, skipped: 0 }); + assert.equal(calls.length, 1); + assert.equal(calls[0].userId, user.userId); + assert.equal(calls[0].message.content, stored.content); + assert.ok(calls[0].message.inboundJobId); + assert.deepEqual( + ctx.db.prepare( + 'SELECT status, attempts, completed_at IS NOT NULL AS completed FROM messaging_inbound_jobs', + ).get(), + { status: 'completed', attempts: 1, completed: 1 }, + ); +}); + +test('does not replay an inbound job whose agent run began before restart', async () => { + const firstManager = new MessagingManager(createIo()); + await firstManager.ingestMessage(user.userId, 'telegram', inboundMessage()); + const job = ctx.db.prepare('SELECT id FROM messaging_inbound_jobs').get(); + const agentId = firstManager._agentId(user.userId, {}); + ctx.db.prepare( + `INSERT INTO agent_runs (id, user_id, agent_id, title, status, error) + VALUES ('interrupted-inbound-run', ?, ?, 'Interrupted inbound run', 'interrupted', 'restart')`, + ).run(user.userId, agentId); + ctx.db.prepare( + `UPDATE messaging_inbound_jobs + SET status = 'processing', attempts = 1, run_id = 'interrupted-inbound-run' + WHERE id = ?`, + ).run(job.id); + + const secondManager = new MessagingManager(createIo()); + let handlerCalls = 0; + secondManager.registerHandler(async () => { + handlerCalls += 1; + }); + secondManager.platforms.set( + secondManager._key(user.userId, agentId, 'telegram'), + { getStatus: () => 'connected' }, + ); + + assert.deepEqual( + await secondManager.recoverPendingInbound(), + { recovered: 0, skipped: 0 }, + ); + assert.equal(handlerCalls, 0); + const persisted = ctx.db.prepare( + 'SELECT status, last_error FROM messaging_inbound_jobs WHERE id = ?', + ).get(job.id); + assert.equal(persisted.status, 'failed'); + assert.match(persisted.last_error, /will not be replayed automatically/i); +}); diff --git a/test/backend/unit/messaging_manager_delivery.test.js b/test/backend/unit/messaging_manager_delivery.test.js index fd62ba95..e9535dab 100644 --- a/test/backend/unit/messaging_manager_delivery.test.js +++ b/test/backend/unit/messaging_manager_delivery.test.js @@ -54,3 +54,81 @@ test('messaging manager rejects an unconfirmed platform delivery before persiste ).get('run-id'); assert.equal(persisted.count, 0); }); + +test('messaging manager forwards cancellation and never starts a pre-aborted delivery', async () => { + const manager = new MessagingManager({ + to() { + return { emit() {} }; + }, + }); + const agentId = manager._agentId(user.userId, {}); + const key = manager._key(user.userId, agentId, 'whatsapp'); + let sendCalls = 0; + manager.platforms.set(key, { + async sendMessage() { + sendCalls += 1; + return { success: true }; + }, + }); + const controller = new AbortController(); + const reason = new Error('caller stopped delivery'); + controller.abort(reason); + + await assert.rejects( + manager.sendMessage(user.userId, 'whatsapp', 'chat-1', 'Never send.', { + agentId, + signal: controller.signal, + }), + (error) => error === reason, + ); + assert.equal(sendCalls, 0); +}); + +test('messaging manager shutdown aborts active delivery and refuses new work', async () => { + const manager = new MessagingManager({ + to() { + return { emit() {} }; + }, + }); + const agentId = manager._agentId(user.userId, {}); + const key = manager._key(user.userId, agentId, 'whatsapp'); + let disconnectCalls = 0; + let forwardedSignal = null; + manager.platforms.set(key, { + sendMessage(_to, _content, options) { + forwardedSignal = options.signal; + return new Promise(() => {}); + }, + async disconnect() { + disconnectCalls += 1; + }, + }); + + const delivery = manager.sendMessage( + user.userId, + 'whatsapp', + 'chat-1', + 'In flight.', + { agentId }, + ); + await new Promise((resolve) => setImmediate(resolve)); + assert.ok(forwardedSignal); + assert.equal(forwardedSignal.aborted, false); + + const firstShutdown = manager.shutdown(); + const secondShutdown = manager.shutdown(); + await assert.rejects( + delivery, + (error) => error.code === 'MESSAGING_SHUTTING_DOWN', + ); + const [firstStatus, secondStatus] = await Promise.all([firstShutdown, secondShutdown]); + + assert.equal(firstStatus.state, 'stopped'); + assert.deepEqual(secondStatus, firstStatus); + assert.equal(forwardedSignal.aborted, true); + assert.equal(disconnectCalls, 1); + await assert.rejects( + manager.sendMessage(user.userId, 'whatsapp', 'chat-1', 'Too late.', { agentId }), + (error) => error.code === 'MESSAGING_SHUTTING_DOWN', + ); +}); diff --git a/test/backend/unit/messaging_progress_supervisor.test.js b/test/backend/unit/messaging_progress_supervisor.test.js index 4de4ddba..9b036fe4 100644 --- a/test/backend/unit/messaging_progress_supervisor.test.js +++ b/test/backend/unit/messaging_progress_supervisor.test.js @@ -299,6 +299,42 @@ describe('messaging progress supervisor', () => { assert.equal(engine.getRunMeta(runId).finalDeliverySent, true); }); + test('final fallback does not retry an ambiguous thrown transport failure', async () => { + const messagingManager = createMessagingManager(); + let attempts = 0; + messagingManager.sendMessage = async () => { + attempts += 1; + const error = new Error('socket closed while awaiting acknowledgement'); + error.code = 'ECONNRESET'; + throw error; + }; + const engine = new AgentEngine(null, { + messagingManager, + messagingDeliveryRetry: { + maxAttempts: 3, + baseDelayMs: 0, + maxDelayMs: 0, + }, + }); + const { runId } = seedMessagingRun(engine); + + await assert.rejects( + engine.deliverMessagingFinalFallback({ + runId, + userId: user.userId, + agentId: null, + platform: 'whatsapp', + chatId: 'chat-1', + content: 'Do not duplicate this result.', + }), + (error) => error.code === 'ECONNRESET' + && error.disableAutonomousRetry === true, + ); + + assert.equal(attempts, 1); + assert.equal(engine.getRunMeta(runId).finalDeliverySent, false); + }); + test('exhausted final delivery cannot trigger an autonomous task replay', async () => { const messagingManager = createMessagingManager(); messagingManager.sendMessage = async () => ({ @@ -711,6 +747,67 @@ describe('messaging progress supervisor', () => { assert.equal(engine.getRunMeta(runId).progressLedger.heartbeatCount, 1); }); + test('overlapping supervisor ticks are coalesced into one heartbeat', async (t) => { + t.mock.timers.enable({ apis: ['Date'] }); + t.mock.timers.setTime(0); + + const messagingManager = createMessagingManager(); + const engine = new AgentEngine(null, { messagingManager }); + let releaseCompose; + const composed = new Promise((resolve) => { releaseCompose = resolve; }); + const { runId } = seedMessagingRun(engine, { + startedAt: 0, + startedAtIso: new Date(0).toISOString(), + composeProgressUpdate: () => composed, + progressLedger: { + currentPhase: 'model', + currentStep: 'model:2', + currentStepStartedAt: new Date(0).toISOString(), + }, + }); + + t.mock.timers.setTime(60_001); + const firstTick = engine.tickMessagingProgressSupervisor(runId); + await new Promise((resolve) => setImmediate(resolve)); + const overlappingTick = await engine.tickMessagingProgressSupervisor(runId); + + assert.equal(overlappingTick.inFlight, true); + releaseCompose('Checked the actual repository state.'); + await firstTick; + assert.equal(messagingManager.sent.length, 1); + }); + + test('a heartbeat composed before finalization is discarded after final delivery wins', async (t) => { + t.mock.timers.enable({ apis: ['Date'] }); + t.mock.timers.setTime(0); + + const messagingManager = createMessagingManager(); + const engine = new AgentEngine(null, { messagingManager }); + let releaseCompose; + const composed = new Promise((resolve) => { releaseCompose = resolve; }); + const { runId } = seedMessagingRun(engine, { + startedAt: 0, + startedAtIso: new Date(0).toISOString(), + composeProgressUpdate: () => composed, + progressLedger: { + currentPhase: 'tool', + currentStep: 'step-1', + currentTool: 'execute_command', + currentStepStartedAt: new Date(0).toISOString(), + }, + }); + + t.mock.timers.setTime(60_001); + const tick = engine.tickMessagingProgressSupervisor(runId); + await new Promise((resolve) => setImmediate(resolve)); + engine.markRunFinalDelivery(runId, 'Finished result.'); + releaseCompose('A stale progress update.'); + + const result = await tick; + assert.equal(result.terminal, true); + assert.equal(messagingManager.sent.length, 0); + }); + test('structured model phases stay visible to the supervisor until they settle', async (t) => { t.mock.timers.enable({ apis: ['Date'] }); t.mock.timers.setTime(0); @@ -745,6 +842,7 @@ describe('messaging progress supervisor', () => { }); assert.equal(engine.getRunMeta(runId).progressLedger.currentPhase, 'model'); + const verifiedBeforeCall = engine.getRunMeta(runId).progressLedger.lastVerifiedProgressAt; assert.equal( engine.getRunMeta(runId).progressLedger.currentStep, 'model:completion_decision', @@ -764,6 +862,11 @@ describe('messaging progress supervisor', () => { assert.equal(engine.getRunMeta(runId).progressLedger.currentPhase, 'idle'); assert.equal(engine.getRunMeta(runId).progressLedger.currentStep, null); + assert.equal( + engine.getRunMeta(runId).progressLedger.lastVerifiedProgressAt, + verifiedBeforeCall, + 'a model self-check is activity, not verified task progress', + ); }); test('runtime heartbeat does not depend on messaging delivery', async (t) => { diff --git a/test/backend/unit/model_catalog.test.js b/test/backend/unit/model_catalog.test.js new file mode 100644 index 00000000..b13bd821 --- /dev/null +++ b/test/backend/unit/model_catalog.test.js @@ -0,0 +1,63 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, beforeEach, describe, test } = require('node:test'); + +const { createTestRuntime, createTestUser, teardownTestRuntime } = require('../../helpers/db'); + +describe('model catalog', () => { + let ctx; + let user; + let agentId; + let originalApiKey; + let originalListModels; + + beforeEach(async () => { + ctx = createTestRuntime(); + user = await createTestUser(ctx.db, { + username: 'model_catalog_user', + password: 'ModelCatalog1!', + email: 'model_catalog_user@example.com', + }); + const { resolveAgentId } = require('../../../server/services/agents/manager'); + const { createDefaultAiSettings } = require('../../../server/services/ai/settings'); + const { OpenAIProvider } = require('../../../server/services/ai/providers/openai'); + agentId = resolveAgentId(user.userId, null); + + const configs = createDefaultAiSettings().ai_provider_configs; + for (const config of Object.values(configs)) config.enabled = false; + configs.openai.enabled = true; + ctx.db.prepare( + `INSERT INTO agent_settings (user_id, agent_id, key, value) + VALUES (?, ?, 'ai_provider_configs', ?)`, + ).run(user.userId, agentId, JSON.stringify(configs)); + + originalApiKey = process.env.OPENAI_API_KEY; + process.env.OPENAI_API_KEY = `catalog-test-${process.pid}-${Date.now()}`; + originalListModels = OpenAIProvider.prototype.listModels; + OpenAIProvider.prototype.listModels = async () => [ + { id: 'gpt-5.3', name: 'gpt-5.3' }, + { id: 'gpt-5.6', name: 'gpt-5.6' }, + ]; + }); + + afterEach(() => { + const { OpenAIProvider } = require('../../../server/services/ai/providers/openai'); + OpenAIProvider.prototype.listModels = originalListModels; + if (originalApiKey === undefined) delete process.env.OPENAI_API_KEY; + else process.env.OPENAI_API_KEY = originalApiKey; + teardownTestRuntime(ctx); + }); + + test('keeps equal raw model ids from different providers independently addressable', async () => { + const { getSupportedModels } = require('../../../server/services/ai/models'); + const models = await getSupportedModels(user.userId, agentId); + + const copilot = models.find((model) => model.id === 'github-copilot::gpt-5.3'); + const openai = models.find((model) => model.id === 'openai::gpt-5.3'); + assert.equal(copilot?.modelId, 'gpt-5.3'); + assert.equal(copilot?.available, false); + assert.equal(openai?.modelId, 'gpt-5.3'); + assert.equal(openai?.available, true); + }); +}); diff --git a/test/backend/unit/model_discovery.test.js b/test/backend/unit/model_discovery.test.js new file mode 100644 index 00000000..c58f66fe --- /dev/null +++ b/test/backend/unit/model_discovery.test.js @@ -0,0 +1,132 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { describe, test } = require('node:test'); + +const { + refreshProviderModelList, +} = require('../../../server/services/ai/model_discovery'); + +let sequence = 0; + +function uniqueProviderId(label) { + sequence += 1; + return `test-${label}-${process.pid}-${sequence}`; +} + +function deferred() { + let resolve; + let reject; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, reject, resolve }; +} + +describe('model discovery', () => { + test('coalesces concurrent refreshes for the same provider runtime', async () => { + const pending = deferred(); + let calls = 0; + class Provider { + constructor() { + this.models = []; + } + + async listModels() { + calls += 1; + return pending.promise; + } + } + + const params = { + providerId: uniqueProviderId('coalesce'), + factory: { Provider, apiKey: true, baseUrl: true }, + apiKey: 'key', + baseUrl: 'https://example.test/v1', + }; + const first = refreshProviderModelList(params); + const second = refreshProviderModelList(params); + pending.resolve([{ id: 'model-a' }]); + + const [left, right] = await Promise.all([first, second]); + assert.equal(calls, 1); + assert.deepEqual(left, right); + assert.equal(left[0].id, 'model-a'); + }); + + test('keys caches by the full credential and base URL identity', async () => { + let calls = 0; + class Provider { + constructor(config) { + this.config = config; + this.models = []; + } + + async listModels() { + calls += 1; + return [{ id: `${this.config.apiKey}@${this.config.baseUrl}` }]; + } + } + + const providerId = uniqueProviderId('keys'); + const factory = { Provider, apiKey: true, baseUrl: true }; + const first = await refreshProviderModelList({ + providerId, + factory, + apiKey: 'abcdefgh-one', + baseUrl: 'https://one.example/v1', + }); + const second = await refreshProviderModelList({ + providerId, + factory, + apiKey: 'abcdefgh-two', + baseUrl: 'https://two.example/v1', + }); + + assert.equal(calls, 2); + assert.notEqual(first[0].id, second[0].id); + }); + + test('lets an aborted caller leave a shared refresh without waiting for the provider', async () => { + const pending = deferred(); + class Provider { + constructor() { + this.models = []; + } + + async listModels() { + return pending.promise; + } + } + + const controller = new AbortController(); + const result = refreshProviderModelList({ + providerId: uniqueProviderId('abort'), + factory: { Provider, apiKey: false, baseUrl: false }, + signal: controller.signal, + }); + controller.abort('request closed'); + + await assert.rejects(result, (error) => error.name === 'AbortError'); + pending.resolve([{ id: 'eventual-model' }]); + }); + + test('uses provider-curated models when discovery fails', async () => { + class Provider { + constructor() { + this.models = ['curated-a', 'curated-b']; + } + + async listModels() { + throw new Error('temporary network failure'); + } + } + + const models = await refreshProviderModelList({ + providerId: uniqueProviderId('fallback'), + factory: { Provider, apiKey: false, baseUrl: false }, + }); + assert.deepEqual(models.map((model) => model.id), ['curated-a', 'curated-b']); + }); +}); diff --git a/test/backend/unit/model_identity.test.js b/test/backend/unit/model_identity.test.js new file mode 100644 index 00000000..9d58b914 --- /dev/null +++ b/test/backend/unit/model_identity.test.js @@ -0,0 +1,51 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { describe, test } = require('node:test'); + +const { + createModelSelectionId, + modelMatchesConfiguredId, + normalizeModelSelections, + resolveModelSelection, + toSelectableModel, +} = require('../../../server/services/ai/model_identity'); + +describe('model identity', () => { + const models = [ + toSelectableModel({ id: 'gpt-5.3', provider: 'github-copilot' }), + toSelectableModel({ id: 'gpt-5.3', provider: 'openai' }), + toSelectableModel({ id: 'openai/gpt-5.3', provider: 'openrouter' }), + ]; + + test('creates provider-scoped ids without altering provider model slugs', () => { + assert.equal(createModelSelectionId('OpenRouter', 'openai/gpt-5.3'), 'openrouter::openai/gpt-5.3'); + assert.deepEqual(models[2], { + id: 'openrouter::openai/gpt-5.3', + modelId: 'openai/gpt-5.3', + provider: 'openrouter', + }); + }); + + test('resolves exact scoped selections before legacy raw ids', () => { + assert.equal(resolveModelSelection(models, 'openai::gpt-5.3'), models[1]); + assert.equal(resolveModelSelection(models, 'gpt-5.3'), models[0]); + assert.equal( + resolveModelSelection(models, 'gpt-5.3', { preferredProvider: 'openai' }), + models[1], + ); + }); + + test('normalizes and deduplicates persisted legacy selections', () => { + assert.deepEqual( + normalizeModelSelections(models, ['gpt-5.3', 'github-copilot::gpt-5.3', 'missing']), + ['github-copilot::gpt-5.3'], + ); + }); + + test('supports raw and scoped deployment allowlists during migration', () => { + assert.equal(modelMatchesConfiguredId(models[1], new Set(['gpt-5.3'])), true); + assert.equal(modelMatchesConfiguredId(models[1], new Set(['openai::gpt-5.3'])), true); + assert.equal(modelMatchesConfiguredId(models[0], new Set(['openai::gpt-5.3'])), false); + }); +}); diff --git a/test/backend/unit/official_integration_reliability.test.js b/test/backend/unit/official_integration_reliability.test.js new file mode 100644 index 00000000..c6e26a00 --- /dev/null +++ b/test/backend/unit/official_integration_reliability.test.js @@ -0,0 +1,332 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, beforeEach, test } = require('node:test'); + +const { + IntegrationManager, +} = require('../../../server/services/integrations/manager'); +const { + createFigmaProvider, +} = require('../../../server/services/integrations/figma/provider'); +const { + createMicrosoftProvider, +} = require('../../../server/services/integrations/microsoft/provider'); +const { + createSlackProvider, +} = require('../../../server/services/integrations/slack/provider'); +const { + createWhatsAppPersonalProvider, +} = require('../../../server/services/integrations/whatsapp/provider'); +const { getAvailableTools } = require('../../../server/services/ai/tools'); + +const originalFetch = global.fetch; +const savedEnvironment = {}; +const ENV_NAMES = [ + 'FIGMA_OAUTH_CLIENT_ID', + 'FIGMA_OAUTH_CLIENT_SECRET', + 'MICROSOFT_OAUTH_CLIENT_ID', + 'MICROSOFT_OAUTH_CLIENT_SECRET', + 'SLACK_OAUTH_CLIENT_ID', + 'SLACK_OAUTH_CLIENT_SECRET', +]; + +function responseJson(value, status = 200) { + return { + ok: status >= 200 && status < 300, + status, + statusText: status === 200 ? 'OK' : 'Error', + headers: { get: () => null }, + text: async () => JSON.stringify(value), + }; +} + +function expiredConnection(credentials) { + return { + id: 91, + user_id: 1, + agent_id: 'main', + provider_key: 'provider', + app_key: 'app', + account_email: 'person@example.test', + status: 'connected', + credentials_json: JSON.stringify({ + ...credentials, + expires_at: new Date(Date.now() - 60_000).toISOString(), + }), + metadata_json: '{}', + }; +} + +beforeEach(() => { + for (const name of ENV_NAMES) { + savedEnvironment[name] = process.env[name]; + process.env[name] = `${name.toLowerCase()}-test-value`; + } +}); + +afterEach(() => { + global.fetch = originalFetch; + for (const name of ENV_NAMES) { + if (savedEnvironment[name] === undefined) delete process.env[name]; + else process.env[name] = savedEnvironment[name]; + } +}); + +test('Figma refreshes an expired token, retries with it, and returns credentials for persistence', async () => { + const calls = []; + const controller = new AbortController(); + global.fetch = async (url, options) => { + calls.push({ url: String(url), options }); + if (String(url).endsWith('/v1/oauth/token')) { + return responseJson({ + access_token: 'figma-access-new', + token_type: 'bearer', + expires_in: 3600, + }); + } + return responseJson({ id: 'figma-user', email: 'person@example.test' }); + }; + + const provider = createFigmaProvider(); + const execution = await provider.executeTool( + 'figma_get_me', + {}, + expiredConnection({ + access_token: 'figma-access-old', + refresh_token: 'figma-refresh', + }), + { signal: controller.signal }, + ); + + assert.equal(execution.result.id, 'figma-user'); + assert.equal(execution.credentials.access_token, 'figma-access-new'); + assert.equal(execution.credentials.refresh_token, 'figma-refresh'); + assert.ok(Date.parse(execution.credentials.expires_at) > Date.now()); + assert.equal(calls.length, 2); + assert.match(calls[0].options.body, /grant_type=refresh_token/); + assert.match(calls[0].options.body, /refresh_token=figma-refresh/); + assert.ok(calls.every((call) => call.options.signal)); +}); + +test('Microsoft refreshes expired credentials before a Graph request', async () => { + const calls = []; + global.fetch = async (url, options) => { + calls.push({ url: String(url), options }); + if (String(url).includes('/oauth2/v2.0/token')) { + return responseJson({ + access_token: 'microsoft-access-new', + refresh_token: 'microsoft-refresh-new', + expires_in: 3600, + scope: 'offline_access Mail.Read', + token_type: 'Bearer', + }); + } + return responseJson({ value: [] }); + }; + + const provider = createMicrosoftProvider(); + const execution = await provider.executeTool( + 'microsoft_365_outlook_list_messages', + {}, + expiredConnection({ + access_token: 'microsoft-access-old', + refresh_token: 'microsoft-refresh-old', + scope: 'offline_access Mail.Read', + }), + ); + + assert.deepEqual(execution.result, { value: [] }); + assert.equal(execution.credentials.access_token, 'microsoft-access-new'); + assert.equal(execution.credentials.refresh_token, 'microsoft-refresh-new'); + assert.equal(calls.length, 2); + assert.match(calls[0].options.body, /grant_type=refresh_token/); +}); + +test('Slack persists both halves of a rotated user token pair', async () => { + const calls = []; + global.fetch = async (url, options) => { + calls.push({ url: String(url), options }); + if (String(url).endsWith('/api/oauth.v2.access')) { + return responseJson({ + ok: true, + access_token: 'bot-access-new', + authed_user: { + access_token: 'user-access-new', + refresh_token: 'user-refresh-new', + expires_in: 43200, + token_type: 'user', + }, + }); + } + return responseJson({ ok: true, channels: [] }); + }; + + const provider = createSlackProvider(); + const execution = await provider.executeTool( + 'slack_list_conversations', + {}, + expiredConnection({ + access_token: 'user-access-old', + refresh_token: 'user-refresh-old', + bot_access_token: 'bot-access-old', + }), + ); + + assert.deepEqual(execution.result, { ok: true, channels: [] }); + assert.equal(execution.credentials.access_token, 'user-access-new'); + assert.equal(execution.credentials.refresh_token, 'user-refresh-new'); + assert.equal(execution.credentials.bot_access_token, 'bot-access-new'); + assert.ok(Date.parse(execution.credentials.expires_at) > Date.now()); + assert.equal(calls.length, 2); +}); + +test('credential-bearing integration executions are serialized by account', async () => { + const manager = Object.create(IntegrationManager.prototype); + manager.connectionExecutionQueues = new Map(); + const connection = { + user_id: 1, + agent_id: 'main', + provider_key: 'slack', + account_email: 'person@example.test', + }; + + const releaseFirst = await manager.acquireConnectionExecution(connection); + let secondAcquired = false; + const second = manager.acquireConnectionExecution(connection).then((release) => { + secondAcquired = true; + return release; + }); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(secondAcquired, false); + + releaseFirst(); + const releaseSecond = await second; + assert.equal(secondAcquired, true); + releaseSecond(); +}); + +test('an aborted queued execution does not break serialization for the next caller', async () => { + const manager = Object.create(IntegrationManager.prototype); + manager.connectionExecutionQueues = new Map(); + const connection = { + user_id: 1, + agent_id: 'main', + provider_key: 'slack', + account_email: 'person@example.test', + }; + const releaseFirst = await manager.acquireConnectionExecution(connection); + const controller = new AbortController(); + const aborted = manager.acquireConnectionExecution(connection, controller.signal); + controller.abort(new Error('queued run stopped')); + await assert.rejects(aborted, /queued run stopped/); + + let thirdAcquired = false; + const third = manager.acquireConnectionExecution(connection).then((release) => { + thirdAcquired = true; + return release; + }); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal(thirdAcquired, false); + + releaseFirst(); + const releaseThird = await third; + assert.equal(thirdAcquired, true); + releaseThird(); +}); + +test('integration manager rethrows cancellation instead of converting it to a tool result', async () => { + const manager = Object.create(IntegrationManager.prototype); + manager.connectionExecutionQueues = new Map(); + const connection = expiredConnection({ access_token: 'token' }); + connection.credentials_json = '{}'; + const cancelled = new Error('run cancelled'); + cancelled.name = 'AbortError'; + cancelled.code = 'ABORT_ERR'; + const provider = { + key: 'fake', + label: 'Fake', + requiresRefreshToken: false, + supportsTool: () => true, + getEnvStatus: () => ({ configured: true }), + getToolDefinitions: () => [{ name: 'fake_read', access: 'read' }], + executeTool: async () => { + throw cancelled; + }, + }; + manager.registry = { list: () => [provider] }; + manager.selectToolConnection = () => ({ connection }); + manager.getConnectionById = () => connection; + manager.parseCredentials = () => ({}); + + await assert.rejects( + manager.executeTool(1, 'fake_read', {}, 'main'), + (error) => error === cancelled, + ); +}); + +test('tool compaction retains official integration access metadata for the agent loop', () => { + const app = { + locals: { + integrationManager: { + getToolDefinitions: () => [{ + name: 'fake_read', + access: 'read', + description: 'Read a fake resource.', + parameters: { type: 'object', properties: {} }, + }], + }, + }, + }; + + const tools = getAvailableTools(app, { userId: 999_999, agentId: 'main' }); + const integrationTool = tools.find((tool) => tool.name === 'fake_read'); + assert.equal(integrationTool?.access, 'read'); +}); + +test('aborting a WhatsApp tool wait does not clear the shared connection attempt', async () => { + const provider = createWhatsAppPersonalProvider(); + const connection = { id: 44 }; + let resolveConnection; + const connectPromise = new Promise((resolve) => { + resolveConnection = resolve; + }); + const client = { + status: 'connecting', + socket: null, + connectPromise, + }; + provider.clients.set(connection.id, client); + const controller = new AbortController(); + const waiting = provider._ensureClient(connection, { signal: controller.signal }); + controller.abort(new Error('WhatsApp tool stopped')); + await assert.rejects(waiting, /WhatsApp tool stopped/); + assert.equal(client.connectPromise, connectPromise); + + client.status = 'connected'; + client.socket = {}; + resolveConnection(); + assert.equal(await provider._ensureClient(connection), client); + await provider.shutdown(); +}); + +test('WhatsApp shutdown clears reconnect work and closes each socket once', async () => { + const provider = createWhatsAppPersonalProvider(); + let closes = 0; + const socket = { end: () => { closes += 1; } }; + const reconnectTimer = setTimeout(() => {}, 60_000); + const sessionTimer = setTimeout(() => {}, 60_000); + provider.reconnectTimers.set(1, reconnectTimer); + provider.sessionReconnectTimers.set('session', sessionTimer); + provider.clients.set(1, { socket, manualDisconnect: false }); + provider.sessions.set('session', { socket }); + + await provider.shutdown(); + + assert.equal(provider.shuttingDown, true); + assert.equal(provider.reconnectTimers.size, 0); + assert.equal(provider.sessionReconnectTimers.size, 0); + assert.equal(provider.clients.size, 0); + assert.equal(provider.sessions.size, 0); + assert.equal(closes, 1); +}); diff --git a/test/backend/unit/ollama_reliability.test.js b/test/backend/unit/ollama_reliability.test.js new file mode 100644 index 00000000..336838ea --- /dev/null +++ b/test/backend/unit/ollama_reliability.test.js @@ -0,0 +1,144 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { OllamaProvider } = require('../../../server/services/ai/providers/ollama'); + +const TOOL = { + name: 'get_weather', + description: 'Read the weather.', + parameters: { type: 'object', properties: {} }, +}; + +function providerWithoutModelLookup() { + const provider = new OllamaProvider({ baseUrl: 'http://ollama.test' }); + provider.ensureModel = async () => true; + return provider; +} + +test('Ollama retries a status-200 tool rejection without tools', async () => { + const provider = providerWithoutModelLookup(); + const originalFetch = global.fetch; + const bodies = []; + global.fetch = async (_url, options) => { + bodies.push(JSON.parse(options.body)); + if (bodies.length === 1) { + return new Response(JSON.stringify({ error: 'this model does not support tools' })); + } + return new Response(JSON.stringify({ + done: true, + message: { content: 'fallback worked' }, + model: 'local-model', + })); + }; + + try { + const result = await provider.chat([{ role: 'user', content: 'hello' }], [TOOL], { + model: 'local-model', + }); + assert.equal(result.content, 'fallback worked'); + assert.equal(bodies.length, 2); + assert.equal(bodies[0].tools.length, 1); + assert.equal('tools' in bodies[1], false); + } finally { + global.fetch = originalFetch; + } +}); + +test('Ollama streaming retries a status-200 tool rejection and parses a final line without a newline', async () => { + const provider = providerWithoutModelLookup(); + const originalFetch = global.fetch; + const bodies = []; + global.fetch = async (_url, options) => { + bodies.push(JSON.parse(options.body)); + if (bodies.length === 1) { + return new Response('{"error":"tools are not supported by this model"}\n'); + } + return new Response([ + JSON.stringify({ message: { content: 'hello ' }, done: false }), + JSON.stringify({ message: { content: 'world' }, done: true, prompt_eval_count: 0, eval_count: 2 }), + ].join('\n')); + }; + + try { + const chunks = []; + for await (const chunk of provider.stream( + [{ role: 'user', content: 'hello' }], + [TOOL], + { model: 'local-model' }, + )) { + chunks.push(chunk); + } + assert.deepEqual(chunks.map((chunk) => chunk.type), ['content', 'content', 'done']); + assert.equal(chunks.at(-1).content, 'hello world'); + assert.deepEqual(chunks.at(-1).usage, { + promptTokens: 0, + completionTokens: 2, + totalTokens: 2, + }); + assert.equal(bodies.length, 2); + assert.equal('tools' in bodies[1], false); + } finally { + global.fetch = originalFetch; + } +}); + +test('Ollama streaming reports an incomplete or malformed stream instead of ending silently', async () => { + const provider = providerWithoutModelLookup(); + const originalFetch = global.fetch; + const responses = [ + new Response('{"message":{"content":"partial"},"done":false}\n'), + new Response('not-json\n'), + ]; + global.fetch = async () => responses.shift(); + + try { + await assert.rejects(async () => { + for await (const _chunk of provider.stream([], [], { model: 'local-model' })) {} + }, (error) => error.code === 'OLLAMA_STREAM_INCOMPLETE'); + await assert.rejects(async () => { + for await (const _chunk of provider.stream([], [], { model: 'local-model' })) {} + }, (error) => error.code === 'OLLAMA_STREAM_MALFORMED'); + } finally { + global.fetch = originalFetch; + } +}); + +test('Ollama streaming cancellation settles even when the reader never does', async () => { + const provider = providerWithoutModelLookup(); + const originalFetch = global.fetch; + let readStarted; + const started = new Promise((resolve) => { readStarted = resolve; }); + const response = { + ok: true, + body: { + getReader() { + return { + read() { + readStarted(); + return new Promise(() => {}); + }, + async cancel() {}, + releaseLock() {}, + }; + }, + }, + }; + global.fetch = async () => response; + const controller = new AbortController(); + + try { + const iterator = provider.stream([], [], { + model: 'local-model', + signal: controller.signal, + }); + const pending = iterator.next(); + await started; + const reason = new Error('stop local generation'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + } finally { + global.fetch = originalFetch; + } +}); diff --git a/test/backend/unit/provider_abort_propagation.test.js b/test/backend/unit/provider_abort_propagation.test.js index 2be3c715..d2af009c 100644 --- a/test/backend/unit/provider_abort_propagation.test.js +++ b/test/backend/unit/provider_abort_propagation.test.js @@ -142,9 +142,11 @@ test('Ollama model discovery and pulls stop when the run is aborted', async () = try { const pending = provider.ensureModel('missing-model', controller.signal); await new Promise((resolve) => setImmediate(resolve)); - assert.equal(pullSignal, controller.signal); + assert.ok(pullSignal); + assert.equal(pullSignal.aborted, false); controller.abort(new Error('ollama aborted')); await assert.rejects(pending, /ollama aborted/); + assert.equal(pullSignal.aborted, true); } finally { global.fetch = originalFetch; } @@ -160,29 +162,56 @@ test('OAuth provider token refreshes forward the run AbortSignal', async () => { }; const pending = refresh('refresh-token', fetchImpl, controller.signal); await new Promise((resolve) => setImmediate(resolve)); - assert.equal(capturedSignal, controller.signal); + assert.ok(capturedSignal); + assert.notEqual(capturedSignal, controller.signal); controller.abort(new Error('oauth refresh aborted')); await assert.rejects(pending, /oauth refresh aborted/); + assert.equal(capturedSignal.aborted, true); } }); -test('GitHub Copilot token refresh forwards the run AbortSignal', async () => { +test('OAuth provider token refresh responses are size bounded', async () => { + const oversizedBody = 'x'.repeat(300 * 1024); + const fetchImpl = async () => new Response(oversizedBody, { status: 200 }); + + for (const refresh of [refreshClaudeCodeAccessToken, refreshGrokOAuthAccessToken]) { + await assert.rejects( + refresh('refresh-token', fetchImpl), + (error) => error.code === 'PROVIDER_OAUTH_RESPONSE_TOO_LARGE', + ); + } +}); + +test('GitHub Copilot caller can stop waiting without cancelling shared refresh', async () => { const originalFetch = global.fetch; const provider = new GithubCopilotProvider({ apiKey: 'test-key' }); const controller = new AbortController(); let capturedSignal; + let resolveFetch; global.fetch = (_url, options) => { capturedSignal = options.signal; - return waitForAbort(capturedSignal); + return new Promise((resolve) => { + resolveFetch = resolve; + }); }; try { const pending = provider._refreshCopilotToken(controller.signal); await new Promise((resolve) => setImmediate(resolve)); - assert.equal(capturedSignal, controller.signal); + assert.ok(capturedSignal); + assert.notEqual(capturedSignal, controller.signal); controller.abort(new Error('copilot refresh aborted')); await assert.rejects(pending, /copilot refresh aborted/); + assert.equal(capturedSignal.aborted, false); + resolveFetch(new Response(JSON.stringify({ + token: 'shared-token', + expires_at: Math.floor(Date.now() / 1000) + 1800, + }), { + status: 200, + headers: { 'content-type': 'application/json' }, + })); + await provider._refreshPromise; } finally { global.fetch = originalFetch; } diff --git a/test/backend/unit/provider_error.test.js b/test/backend/unit/provider_error.test.js new file mode 100644 index 00000000..eaa1881f --- /dev/null +++ b/test/backend/unit/provider_error.test.js @@ -0,0 +1,47 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { isTransientError } = require('../../../server/services/ai/providerRetry'); +const { + sanitizeProviderErrorDetail, + wrapProviderError, +} = require('../../../server/services/ai/providers/provider_error'); + +test('provider error wrapping preserves retry metadata and cause', () => { + const original = new Error('overloaded'); + original.status = 529; + original.code = 'provider_overloaded'; + original.headers = { 'retry-after': '2' }; + + const wrapped = wrapProviderError(original, 'Remote model failed'); + + assert.equal(wrapped.cause, original); + assert.equal(wrapped.status, 529); + assert.equal(wrapped.code, 'provider_overloaded'); + assert.equal(wrapped.headers, original.headers); + assert.equal(isTransientError(wrapped), true); +}); + +test('provider error wrapping preserves caller cancellation identity', () => { + const controller = new AbortController(); + const reason = new Error('run interrupted'); + controller.abort(reason); + + assert.equal( + wrapProviderError(new Error('SDK abort wrapper'), 'Provider failed', { + signal: controller.signal, + }), + reason, + ); +}); + +test('provider error details redact credentials and query keys', () => { + const sanitized = sanitizeProviderErrorDetail( + 'Bearer secret-token https://api.example.test/models?key=private-key access_token=abc', + ); + + assert.doesNotMatch(sanitized, /secret-token|private-key|access_token=abc/); + assert.match(sanitized, /Bearer \[redacted\]/); +}); diff --git a/test/backend/unit/provider_retry.test.js b/test/backend/unit/provider_retry.test.js index 5f8d72d0..c28dd282 100644 --- a/test/backend/unit/provider_retry.test.js +++ b/test/backend/unit/provider_retry.test.js @@ -127,3 +127,30 @@ test('withProviderRetry honors a custom isRetryable guard', async () => { ); assert.equal(calls, 1); }); + +test('withProviderRetry aborts during backoff without starting another attempt', async () => { + const controller = new AbortController(); + let calls = 0; + const retry = withProviderRetry( + async () => { + calls += 1; + const err = new Error('temporarily unavailable'); + err.status = 503; + throw err; + }, + { + baseDelayMs: 10_000, + maxDelayMs: 10_000, + maxAttempts: 3, + signal: controller.signal, + }, + ); + + await new Promise((resolve) => setImmediate(resolve)); + controller.abort('run stopped'); + await assert.rejects( + retry, + (error) => error.name === 'AbortError' && error.code === 'ABORT_ERR', + ); + assert.equal(calls, 1); +}); diff --git a/test/backend/unit/provider_selector.test.js b/test/backend/unit/provider_selector.test.js new file mode 100644 index 00000000..f63639aa --- /dev/null +++ b/test/backend/unit/provider_selector.test.js @@ -0,0 +1,109 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { afterEach, beforeEach, describe, test } = require('node:test'); + +const { createTestRuntime, createTestUser, teardownTestRuntime } = require('../../helpers/db'); + +describe('provider selector', () => { + let ctx; + let user; + let agentId; + let modelsModule; + let originalCreateProviderInstance; + let originalGetSupportedModels; + let getProviderForUser; + + const catalog = [ + { + id: 'github-copilot::gpt-5.3', + modelId: 'gpt-5.3', + provider: 'github-copilot', + purpose: 'general', + available: true, + }, + { + id: 'openai::gpt-5.3', + modelId: 'gpt-5.3', + provider: 'openai', + purpose: 'general', + available: true, + }, + ]; + + function setSetting(key, value) { + ctx.db.prepare( + `INSERT INTO agent_settings (user_id, agent_id, key, value) + VALUES (?, ?, ?, ?) + ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value`, + ).run(user.userId, agentId, key, JSON.stringify(value)); + } + + beforeEach(async () => { + ctx = createTestRuntime(); + user = await createTestUser(ctx.db, { + username: 'provider_selector_user', + password: 'ProviderSelector1!', + email: 'provider_selector_user@example.com', + }); + const { resolveAgentId } = require('../../../server/services/agents/manager'); + agentId = resolveAgentId(user.userId, null); + + modelsModule = require('../../../server/services/ai/models'); + originalCreateProviderInstance = modelsModule.createProviderInstance; + originalGetSupportedModels = modelsModule.getSupportedModels; + modelsModule.getSupportedModels = async () => catalog; + modelsModule.createProviderInstance = (provider) => ({ provider }); + + delete require.cache[require.resolve('../../../server/services/ai/provider_selector')]; + ({ getProviderForUser } = require('../../../server/services/ai/provider_selector')); + }); + + afterEach(() => { + modelsModule.createProviderInstance = originalCreateProviderInstance; + modelsModule.getSupportedModels = originalGetSupportedModels; + delete require.cache[require.resolve('../../../server/services/ai/provider_selector')]; + teardownTestRuntime(ctx); + }); + + test('routes equal raw model ids to the explicitly selected provider', async () => { + setSetting('enabled_models', catalog.map((model) => model.id)); + setSetting('default_chat_model', 'openai::gpt-5.3'); + + const selected = await getProviderForUser(user.userId, '', false, null, { agentId }); + assert.equal(selected.providerName, 'openai'); + assert.equal(selected.model, 'gpt-5.3'); + assert.equal(selected.modelSelectionId, 'openai::gpt-5.3'); + }); + + test('keeps deterministic catalog ordering for legacy raw settings', async () => { + setSetting('enabled_models', ['gpt-5.3']); + setSetting('default_chat_model', 'gpt-5.3'); + + const selected = await getProviderForUser(user.userId, '', false, null, { agentId }); + assert.equal(selected.providerName, 'github-copilot'); + assert.equal(selected.modelSelectionId, 'github-copilot::gpt-5.3'); + }); + + test('allows an explicit available fallback outside the smart-selector pool', async () => { + setSetting('enabled_models', ['github-copilot::gpt-5.3']); + + const selected = await getProviderForUser( + user.userId, + '', + false, + 'openai::gpt-5.3', + { agentId }, + ); + assert.equal(selected.providerName, 'openai'); + }); + + test('does not silently widen an explicitly configured but invalid model pool', async () => { + setSetting('enabled_models', ['missing-provider::missing-model']); + + await assert.rejects( + getProviderForUser(user.userId, '', false, null, { agentId }), + /None of the enabled AI models are currently available/, + ); + }); +}); diff --git a/test/backend/unit/run_recovery.test.js b/test/backend/unit/run_recovery.test.js index 288c908a..7e79b160 100644 --- a/test/backend/unit/run_recovery.test.js +++ b/test/backend/unit/run_recovery.test.js @@ -84,3 +84,27 @@ test('stopServices interrupts active runs before shutdown continues', async () = assert.equal(interrupted, 1); }); + +test('stopServices uses the engine shutdown barrier when available', async () => { + ctx = createTestRuntime(); + const { stopServices } = require('../../../server/services/manager'); + let shutdowns = 0; + let interrupts = 0; + + await stopServices({ + locals: { + agentEngine: { + async shutdown() { + shutdowns += 1; + return { state: 'stopped', timedOut: false, pendingCount: 0 }; + }, + interruptAllActiveRuns() { + interrupts += 1; + }, + }, + }, + }); + + assert.equal(shutdowns, 1); + assert.equal(interrupts, 0); +}); diff --git a/test/backend/unit/runtime_http_client.test.js b/test/backend/unit/runtime_http_client.test.js new file mode 100644 index 00000000..631dbff6 --- /dev/null +++ b/test/backend/unit/runtime_http_client.test.js @@ -0,0 +1,116 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const http = require('node:http'); +const { afterEach, test } = require('node:test'); + +const { + RuntimeHttpClient, + VmBrowserProvider, +} = require('../../../server/services/runtime/backends/local-vm'); + +const servers = new Set(); + +afterEach(async () => { + await Promise.allSettled(Array.from(servers, (server) => new Promise((resolve) => { + server.closeAllConnections?.(); + server.close(resolve); + }))); + servers.clear(); +}); + +async function listen(handler) { + const server = http.createServer(handler); + servers.add(server); + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + const address = server.address(); + return { server, url: `http://127.0.0.1:${address.port}` }; +} + +test('runtime HTTP requests abort immediately when a run is stopped', async () => { + const { url } = await listen(() => {}); + const client = new RuntimeHttpClient(url); + const controller = new AbortController(); + const request = client.request('GET', '/hang', undefined, { + retryCount: 0, + signal: controller.signal, + }); + controller.abort('run stopped'); + + await assert.rejects( + request, + (error) => error.name === 'AbortError' && error.code === 'ABORT_ERR', + ); +}); + +test('runtime retry backoff is cancellable', async () => { + const { url } = await listen((request) => request.socket.destroy()); + const client = new RuntimeHttpClient(url); + const controller = new AbortController(); + const startedAt = Date.now(); + const request = client.request('GET', '/disconnect', undefined, { + retryCount: 5, + retryDelayMs: 5000, + signal: controller.signal, + }); + setTimeout(() => controller.abort('run stopped'), 50); + + await assert.rejects(request, (error) => error.name === 'AbortError'); + assert.ok(Date.now() - startedAt < 1000); +}); + +test('runtime HTTP client never automatically replays side-effecting requests', async () => { + let requests = 0; + const { url } = await listen((request, response) => { + requests += 1; + if (requests === 1) { + request.socket.destroy(); + return; + } + response.writeHead(200, { 'content-type': 'application/json' }); + response.end('{"ok":true}'); + }); + const client = new RuntimeHttpClient(url); + + await assert.rejects( + client.request('POST', '/click', { selector: '#submit' }), + ); + assert.equal(requests, 1); +}); + +test('VM browser transports cancellation out of the serialized request body', async () => { + const calls = []; + const client = { + async request(method, pathname, body, options) { + calls.push({ method, pathname, body, options }); + return { url: body.url }; + }, + }; + const provider = new VmBrowserProvider(client); + const controller = new AbortController(); + + await provider.navigate('https://example.com', { signal: controller.signal }); + + assert.equal(calls.length, 1); + assert.equal(calls[0].body.signal, undefined); + assert.equal(calls[0].options.signal, controller.signal); +}); + +test('VM browser status and cookie reads forward cancellation', async () => { + const calls = []; + const client = { + async request(method, pathname, body, options) { + calls.push({ method, pathname, body, options }); + if (pathname === '/browser/status') return { launched: true, pages: 1 }; + return { cookies: [] }; + }, + }; + const provider = new VmBrowserProvider(client); + const controller = new AbortController(); + + await provider.getPageInfo({ signal: controller.signal }); + await provider.getCookies({ signal: controller.signal }); + + assert.equal(calls.length, 2); + assert.ok(calls.every((call) => call.options.signal === controller.signal)); +}); diff --git a/test/backend/unit/runtime_manager.test.js b/test/backend/unit/runtime_manager.test.js new file mode 100644 index 00000000..1f77b0cd --- /dev/null +++ b/test/backend/unit/runtime_manager.test.js @@ -0,0 +1,71 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { RuntimeManager } = require('../../../server/services/runtime/manager'); + +test('desktop CLI commands execute on the companion provider, never the server worker', async () => { + const calls = []; + const manager = Object.create(RuntimeManager.prototype); + manager.shellWorkerPool = { + async execute() { + throw new Error('server shell worker must not execute a desktop command'); + }, + }; + manager.getCliProviderForUser = async () => ({ + backend: 'desktop-companion', + async execute(command, options) { + calls.push({ command, options }); + return { stdout: 'from companion', stderr: '', exitCode: 0 }; + }, + }); + + const result = await manager.executeCliCommand(7, 'pwd', { timeout: 1234 }); + + assert.equal(result.backend, 'desktop-companion'); + assert.equal(result.stdout, 'from companion'); + assert.deepEqual(calls, [{ command: 'pwd', options: { timeout: 1234 } }]); +}); + +test('interactive desktop CLI commands retain their input sequence', async () => { + const calls = []; + const manager = Object.create(RuntimeManager.prototype); + manager.getCliProviderForUser = async () => ({ + backend: 'desktop-companion', + async execute() { + throw new Error('interactive command used the non-interactive path'); + }, + async executeInteractive(command, inputs, options) { + calls.push({ command, inputs, options }); + return { stdout: 'interactive companion', stderr: '', exitCode: 0 }; + }, + }); + const options = { pty: true, inputs: ['yes\n'], timeout: 5000 }; + + const result = await manager.executeCliCommand(7, 'confirm', options); + + assert.equal(result.backend, 'desktop-companion'); + assert.deepEqual(calls, [{ command: 'confirm', inputs: ['yes\n'], options }]); +}); + +test('Android providers are stable per user so concurrent starts share one controller', async () => { + const created = []; + const manager = Object.create(RuntimeManager.prototype); + manager.androidControllers = new Map(); + manager.createAndroidController = (userId) => { + const controller = { userId }; + created.push(controller); + return controller; + }; + + const [first, second, other] = await Promise.all([ + manager.getAndroidProviderForUser(7), + manager.getAndroidProviderForUser('7'), + manager.getAndroidProviderForUser(8), + ]); + + assert.equal(first, second); + assert.notEqual(first, other); + assert.equal(created.length, 2); +}); diff --git a/test/backend/unit/runtime_paths.test.js b/test/backend/unit/runtime_paths.test.js new file mode 100644 index 00000000..633afc58 --- /dev/null +++ b/test/backend/unit/runtime_paths.test.js @@ -0,0 +1,39 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { test } = require('node:test'); + +const { + removeEnvValue, + upsertEnvValue, +} = require('../../../runtime/paths'); + +test('runtime env updates are sanitized and atomically replace the target', (t) => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'neoagent-runtime-paths-')); + t.after(() => fs.rmSync(directory, { recursive: true, force: true })); + const envFile = path.join(directory, '.env'); + fs.writeFileSync(envFile, 'FIRST=one\nTOKEN=old\n', { mode: 0o600 }); + + upsertEnvValue(envFile, 'TOKEN', 'new\r\nINJECTED=value'); + assert.equal( + fs.readFileSync(envFile, 'utf8'), + 'FIRST=one\nTOKEN=newINJECTED=value\n', + ); + assert.deepEqual( + fs.readdirSync(directory).sort(), + ['.env'], + ); + if (process.platform !== 'win32') { + assert.equal(fs.statSync(envFile).mode & 0o777, 0o600); + } + + assert.equal(removeEnvValue(envFile, 'TOKEN'), true); + assert.equal(fs.readFileSync(envFile, 'utf8'), 'FIRST=one\n'); + assert.throws( + () => upsertEnvValue(envFile, 'INVALID-KEY', 'value'), + /Invalid environment variable name/, + ); +}); diff --git a/test/backend/unit/social_reach.test.js b/test/backend/unit/social_reach.test.js index f24dd856..fb09af8f 100644 --- a/test/backend/unit/social_reach.test.js +++ b/test/backend/unit/social_reach.test.js @@ -140,6 +140,42 @@ test('social reach reads reddit public JSON posts and comments', async () => { } }); +test('RSS reader rejects hosts resolving to private networks before connecting', async () => { + const { fetchText } = require('../../../server/services/social_reach/utils'); + await assert.rejects( + fetchText('https://feed.example/rss.xml', { + publicOnly: true, + lookup: async () => [{ address: '127.0.0.1', family: 4 }], + }), + /resolves to a private, loopback, or reserved address/, + ); +}); + +test('social reach forwards caller cancellation to channel requests', async () => { + const ctx = createTestRuntime(); + const { SocialReachService } = loadSocialReach(); + const service = new SocialReachService(); + let receivedSignal = null; + service.channelById.set('github', { + async read(args) { + receivedSignal = args.signal; + return new Promise((resolve, reject) => { + receivedSignal.addEventListener('abort', () => reject(receivedSignal.reason), { once: true }); + }); + }, + }); + const controller = new AbortController(); + const reason = new Error('request disconnected'); + const pending = service.read(1, { platform: 'github' }, { signal: controller.signal }); + controller.abort(reason); + try { + await assert.rejects(pending, (error) => error === reason); + assert.equal(receivedSignal, controller.signal); + } finally { + teardownTestRuntime(ctx); + } +}); + test('social reach cookie bundles are encrypted at rest', async () => { const ctx = createTestRuntime(); const { readCookieBundle, writeCookieBundle } = require('../../../server/services/social_reach/store'); diff --git a/test/backend/unit/social_video_reliability.test.js b/test/backend/unit/social_video_reliability.test.js new file mode 100644 index 00000000..648ed799 --- /dev/null +++ b/test/backend/unit/social_video_reliability.test.js @@ -0,0 +1,38 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { SocialVideoService } = require('../../../server/services/social_video/service'); + +test('social video cancellation reaches dependency commands and is not shaped as success', async () => { + const commandSignals = []; + const service = new SocialVideoService({ + cliExecutor: { + execute(_command, options) { + commandSignals.push(options.signal); + return new Promise((resolve) => { + options.signal.addEventListener('abort', () => resolve({ + exitCode: null, + stdout: '', + stderr: '', + aborted: true, + }), { once: true }); + }); + }, + }, + }); + const controller = new AbortController(); + const reason = new Error('agent run stopped'); + const pending = service.extractFromUrl( + 1, + 'https://www.youtube.com/watch?v=abc123', + { signal: controller.signal }, + ); + await new Promise((resolve) => setImmediate(resolve)); + controller.abort(reason); + + await assert.rejects(pending, (error) => error === reason); + assert.equal(commandSignals.length, 2); + assert.ok(commandSignals.every((signal) => signal === controller.signal)); +}); diff --git a/test/backend/unit/subagent_limits.test.js b/test/backend/unit/subagent_limits.test.js index fabe752f..20cd12f0 100644 --- a/test/backend/unit/subagent_limits.test.js +++ b/test/backend/unit/subagent_limits.test.js @@ -17,6 +17,7 @@ test('spawnSubagent enforces the per-run subagent cap', async () => { userId, agentId: null, subagentDepth: 0, + status: 'running', }); for (let i = 0; i < 10; i += 1) { @@ -45,6 +46,7 @@ test('spawnSubagent blocks recursive child spawning', async () => { userId, agentId: null, subagentDepth: 1, + status: 'running', }); const result = await engine.spawnSubagent(userId, 'child-run', 'Try to spawn again'); @@ -52,3 +54,38 @@ test('spawnSubagent blocks recursive child spawning', async () => { assert.match(result.error || '', /cannot spawn additional sub-agents/i); assert.equal(engine.subagents.size, 0); }); + +test('a failed child settles its record without an unhandled rejection', async (t) => { + const originalRunWithModel = AgentEngine.prototype.runWithModel; + AgentEngine.prototype.runWithModel = async () => { + throw new Error('child provider failed'); + }; + t.after(() => { + AgentEngine.prototype.runWithModel = originalRunWithModel; + }); + + const userId = 0; + const engine = new AgentEngine(null, { + memoryManager: { + recallMemory: async () => [], + }, + }); + engine.emit = () => {}; + engine.activeRuns.set('parent-run', { + userId, + agentId: null, + subagentDepth: 0, + status: 'running', + aborted: false, + abortController: new AbortController(), + }); + + const started = await engine.spawnSubagent(userId, 'parent-run', 'Investigate failure'); + const record = engine.subagents.get(started.handle); + const settled = await record.promise; + + assert.equal(settled, record); + assert.equal(record.status, 'failed'); + assert.equal(record.error, 'child provider failed'); + assert.equal(record.settled, true); +}); diff --git a/test/backend/unit/task_integration_cancellation.test.js b/test/backend/unit/task_integration_cancellation.test.js new file mode 100644 index 00000000..ee9d9d96 --- /dev/null +++ b/test/backend/unit/task_integration_cancellation.test.js @@ -0,0 +1,38 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { + fetchTriggerRows, +} = require('../../../server/services/tasks/integration_runtime'); + +test('integration trigger polling forwards caller cancellation to the provider', async () => { + const controller = new AbortController(); + let executionOptions = null; + const integrationManager = { + executeTool(_userId, _toolName, _args, _agentId, options) { + executionOptions = options; + return new Promise((_, reject) => { + options.signal.addEventListener('abort', () => reject(options.signal.reason), { + once: true, + }); + }); + }, + }; + + const pending = fetchTriggerRows({ + integrationManager, + userId: null, + agentId: null, + triggerType: 'slack_message_received', + config: { connectionId: 7, channel: 'C123' }, + signal: controller.signal, + }); + await new Promise((resolve) => setImmediate(resolve)); + + const reason = new Error('poller stopped'); + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.equal(executionOptions.signal, controller.signal); +}); diff --git a/test/backend/unit/task_runtime_delivery.test.js b/test/backend/unit/task_runtime_delivery.test.js index 176e9fa7..31e0f0d3 100644 --- a/test/backend/unit/task_runtime_delivery.test.js +++ b/test/backend/unit/task_runtime_delivery.test.js @@ -576,6 +576,51 @@ describe('scheduled task result delivery', () => { assert.deepEqual(runState.sentMessages, ['Finished result.']); }); + test('send_message cannot terminate the originating run with a progress-only promise', async () => { + const { executeTool } = require('../../../server/services/ai/tools'); + const sent = []; + const runState = { + messagingSent: false, + explicitMessageSent: false, + finalDeliverySent: false, + sentMessages: [], + }; + const engine = { + activeRuns: new Map([['run-id', runState]]), + messagingManager: { + async sendMessage(...args) { + sent.push(args); + return { success: true }; + }, + }, + async stopMessagingProgressSupervisor() {}, + }; + const context = { + userId: user.userId, + runId: 'run-id', + triggerSource: 'messaging', + source: 'whatsapp', + chatId: '49123456789@s.whatsapp.net', + }; + + const result = await executeTool('send_message', { + platform: 'whatsapp', + to: '49123456789', + content: "I'm working on that and will update you.", + }, context, engine); + + assert.match(result.error, /cannot end the run with a promise/); + assert.equal(sent.length, 0); + assert.equal(runState.finalDeliverySent, false); + + await executeTool('send_message', { + platform: 'whatsapp', + to: '49987654321', + content: "I'm working on that and will update you.", + }, context, engine); + assert.equal(sent.length, 1, 'a requested message to a third party is not reclassified'); + }); + test('task runtime start is idempotent and reports truthful state', async () => { const cronHarness = createCronHarness(); runtime = new TaskRuntime( @@ -651,7 +696,7 @@ describe('scheduled task result delivery', () => { assert.equal(pollCount, 1); }); - test('task runtime waits for active execution and rejects new work during shutdown', async () => { + test('task runtime cancels active execution results and rejects new work during shutdown', async () => { const cronHarness = createCronHarness(); const runStarted = deferred(); const releaseRun = deferred(); @@ -698,8 +743,33 @@ describe('scheduled task result delivery', () => { assert.equal(runtime.getStatus().state, 'stopping'); releaseRun.resolve(); - assert.equal((await execution).content, 'Completed before shutdown.'); + assert.deepEqual(await execution, { + skipped: true, + reason: 'runtime_stopping', + runId: null, + }); assert.equal((await stopping).state, 'stopped'); + + let pollCalled = false; + assert.deepEqual( + runtime.runTaskNow(task.id, user.userId), + { running: false, skipped: true, reason: 'runtime_stopping' }, + ); + assert.deepEqual( + await runtime._executeTask(task.id, user.userId, { + manual: true, + triggerType: 'schedule', + triggerSource: 'manual', + }), + { skipped: true, reason: 'runtime_stopping' }, + ); + assert.deepEqual( + await runtime._runPoll('after_stop', async () => { + pollCalled = true; + }, () => {}), + { skipped: true, reason: 'runtime_stopping' }, + ); + assert.equal(pollCalled, false); }); test('deletes a completed one-time task after its due poll', async () => { diff --git a/test/backend/unit/terminal_reply.test.js b/test/backend/unit/terminal_reply.test.js new file mode 100644 index 00000000..b950a088 --- /dev/null +++ b/test/backend/unit/terminal_reply.test.js @@ -0,0 +1,75 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { isDirectAnswerEligibleAnalysis } = require('../../../server/services/ai/taskAnalysis'); +const { + enforceTerminalReplyDecision, +} = require('../../../server/services/ai/loop/completion_judge'); +const { isDeferredWorkReply } = require('../../../server/services/ai/terminal_reply'); + +test('detects progress-only replies that must not terminate a run', () => { + const deferred = [ + "I'm working on that.", + 'I am still checking the logs.', + "Sure, I'll investigate and get back to you.", + 'Let me run the tests.', + 'Give me a moment.', + 'Working on it…', + "I’ll update you once I know more.", + ]; + + for (const reply of deferred) { + assert.equal(isDeferredWorkReply(reply), true, reply); + } +}); + +test('does not confuse concrete answers or real blockers with progress-only replies', () => { + const terminal = [ + 'The tests pass and the fix is in server.js.', + "I couldn't continue because your API key is missing. Please provide it.", + "I'm working on a novel about Berlin.", + 'I’ll remind you tomorrow at 09:00.', + ]; + + for (const reply of terminal) { + assert.equal(isDeferredWorkReply(reply), false, reply); + } +}); + +test('completion decisions cannot mark a progress-only reply complete or blocked', () => { + assert.deepEqual( + enforceTerminalReplyDecision( + { status: 'complete', reason: 'Looks done.' }, + "I'm working on that and will update you.", + ), + { + status: 'continue', + reason: 'The latest reply only announces or promises unfinished work; the run must continue or return a concrete blocker.', + }, + ); + assert.deepEqual( + enforceTerminalReplyDecision( + { status: 'blocked', reason: 'Need a minute.' }, + 'Give me a moment.', + ).status, + 'continue', + ); +}); + +test('task analysis cannot fast-path a deferred-work draft as a direct answer', () => { + const analysis = { + mode: 'direct_answer', + verification_need: 'none', + freshness_risk: 'none', + planning_depth: 'none', + needs_subagents: false, + draft_reply: "I'll check that now.", + }; + assert.equal(isDirectAnswerEligibleAnalysis(analysis), false); + assert.equal( + isDirectAnswerEligibleAnalysis({ ...analysis, draft_reply: 'The answer is 42.' }), + true, + ); +}); diff --git a/test/backend/unit/tool_evidence.test.js b/test/backend/unit/tool_evidence.test.js index d3109292..aa78ec24 100644 --- a/test/backend/unit/tool_evidence.test.js +++ b/test/backend/unit/tool_evidence.test.js @@ -6,6 +6,7 @@ const { test } = require('node:test'); const { classifyToolExecution, deriveEvidenceSource, + gatheredNewEvidence, isSubstantiveProgressEvidence, isSubstantiveProgressToolName, summarizeProgressToolExecutions, @@ -14,6 +15,18 @@ const { inferToolFailureMessage, buildAutonomousRecoveryContext, } = require('../../../server/services/ai/toolEvidence'); +const { + isReadOnlyToolCall, +} = require('../../../server/services/ai/loop/tool_dispatch'); + +function toolCall(name, args = {}) { + return { + function: { + name, + arguments: JSON.stringify(args), + }, + }; +} test('deriveEvidenceSource maps each tool family to its bucket', () => { const cases = { @@ -64,6 +77,108 @@ test('classifyToolExecution tags evidence source, relevance, and state change', assert.equal(browserClass.stateChanged, true); }); +test('official integration access metadata drives evidence and state classification', () => { + const notionRead = classifyToolExecution( + 'notion_search', + { query: 'roadmap' }, + { results: [{ id: 'page-1' }] }, + '', + { name: 'notion_search', access: 'read' }, + ); + assert.equal(notionRead.evidenceSource, 'integration'); + assert.equal(notionRead.evidenceRelevant, true); + assert.equal(notionRead.stateChanged, false); + assert.equal(gatheredNewEvidence(notionRead, { unchangedCount: 1 }), true); + assert.equal(gatheredNewEvidence(notionRead, { unchangedCount: 2 }), false); + + const notionWrite = classifyToolExecution( + 'notion_create_page', + {}, + { id: 'page-2' }, + '', + { name: 'notion_create_page', access: 'write' }, + ); + assert.equal(notionWrite.evidenceSource, 'integration'); + assert.equal(notionWrite.stateChanged, true); + + const dynamicRead = classifyToolExecution( + 'notion_api_request', + { method: 'GET' }, + { results: [] }, + '', + { name: 'notion_api_request', access: 'dynamic_http_method' }, + ); + const dynamicWrite = classifyToolExecution( + 'notion_api_request', + { method: 'PATCH' }, + { id: 'page-3' }, + '', + { name: 'notion_api_request', access: 'dynamic_http_method' }, + ); + assert.equal(dynamicRead.stateChanged, false); + assert.equal(dynamicWrite.stateChanged, true); +}); + +test('official integration access metadata safely enables parallel read batches', () => { + assert.equal( + isReadOnlyToolCall( + toolCall('google_workspace_gmail_get_thread', { thread_id: 'thread-1' }), + { access: 'read' }, + ), + true, + ); + assert.equal( + isReadOnlyToolCall( + toolCall('google_workspace_gmail_send_email', { to: ['person@example.test'] }), + { access: 'write' }, + ), + false, + ); + assert.equal( + isReadOnlyToolCall( + toolCall('notion_api_request', { method: 'GET', path: '/v1/users' }), + { access: 'dynamic_http_method' }, + ), + true, + ); + assert.equal( + isReadOnlyToolCall( + toolCall('notion_api_request', { method: 'PATCH', path: '/v1/pages/page-1' }), + { access: 'dynamic_http_method' }, + ), + false, + ); + + const { AgentEngine } = require('../../../server/services/ai/loop/agent_engine_core'); + const engine = new AgentEngine(null); + assert.equal( + engine.isReadOnlyToolCall(toolCall('notion_search'), { access: 'read' }), + true, + ); +}); + +test('new MCP and device reads count as evidence without pretending they mutate state', () => { + const mcpRead = classifyToolExecution( + 'linear_search_issues', + { query: 'reliability' }, + { issues: [{ id: 'ENG-1' }] }, + ); + assert.equal(mcpRead.evidenceRelevant, true); + assert.equal(mcpRead.stateChanged, false); + assert.equal(gatheredNewEvidence(mcpRead, { unchangedCount: 1 }), true); + + const androidRead = classifyToolExecution('android_screenshot', {}, { image: 'artifact' }); + const desktopRead = classifyToolExecution('desktop_observe', {}, { tree: [] }); + assert.equal(androidRead.stateChanged, false); + assert.equal(desktopRead.stateChanged, false); + assert.equal(androidRead.evidenceRelevant, true); + assert.equal(desktopRead.evidenceRelevant, true); + + assert.equal(classifyToolExecution('android_tap', {}, { success: true }).stateChanged, true); + assert.equal(classifyToolExecution('desktop_click', {}, { success: true }).stateChanged, true); + assert.equal(classifyToolExecution('think', {}, { thought: 'still thinking' }).evidenceRelevant, false); +}); + test('classifyToolExecution derives failure from execute_command exit code', () => { const failed = classifyToolExecution('execute_command', { command: 'x' }, { exitCode: 1, @@ -135,6 +250,7 @@ test('summarizeToolExecutions renders a numbered status list', () => { test('progress evidence excludes communication and meta-only tool activity', () => { assert.equal(isSubstantiveProgressToolName('send_message'), false); assert.equal(isSubstantiveProgressToolName('send_interim_update'), false); + assert.equal(isSubstantiveProgressToolName('notify_user'), false); assert.equal(isSubstantiveProgressToolName('think'), false); assert.equal(isSubstantiveProgressToolName('read_file'), true); diff --git a/test/backend/unit/voice_cancellation.test.js b/test/backend/unit/voice_cancellation.test.js new file mode 100644 index 00000000..36ad3fe7 --- /dev/null +++ b/test/backend/unit/voice_cancellation.test.js @@ -0,0 +1,159 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { test } = require('node:test'); + +const { + createTestRuntime, + teardownTestRuntime, +} = require('../../helpers/db'); +const { runWithAbortTimeout } = require('../../../server/utils/abort'); +const { VoiceLiveSession } = require('../../../server/services/voice/liveSession'); +const { synthesizeSpeechBuffer } = require('../../../server/services/voice/openaiSpeech'); +const { + synthesizeVoiceReply, + transcribeVoiceInput, +} = require('../../../server/services/voice/providers'); + +test('voice provider entry points preserve a pre-aborted caller reason', async () => { + const controller = new AbortController(); + const reason = new Error('voice turn interrupted'); + controller.abort(reason); + + await assert.rejects( + synthesizeVoiceReply('hello', { + provider: 'gemini', + apiKey: 'unused', + signal: controller.signal, + }), + (error) => error === reason, + ); + await assert.rejects( + transcribeVoiceInput('/path/does/not/need/to/exist', { + provider: 'gemini', + apiKey: 'unused', + signal: controller.signal, + }), + (error) => error === reason, + ); +}); + +test('abort timeout rejects even when an SDK ignores its signal', async () => { + let operationSignal = null; + await assert.rejects( + runWithAbortTimeout((signal) => { + operationSignal = signal; + return new Promise(() => {}); + }, { + timeoutMs: 10, + timeoutCode: 'VOICE_TEST_TIMEOUT', + label: 'Voice test', + }), + (error) => error.code === 'VOICE_TEST_TIMEOUT', + ); + assert.ok(operationSignal); + assert.equal(operationSignal.aborted, true); +}); + +test('OpenAI speech synthesis bounds response bytes while streaming', async () => { + const client = { + audio: { + speech: { + create: async () => new Response(new Uint8Array(9)), + }, + }, + }; + + await assert.rejects( + synthesizeSpeechBuffer(client, 'hello', { maxResponseBytes: 8 }), + (error) => error.code === 'VOICE_PROVIDER_RESPONSE_TOO_LARGE', + ); +}); + +test('OpenAI speech synthesis times out even when the SDK ignores cancellation', async () => { + let capturedSignal = null; + const client = { + audio: { + speech: { + create: (_payload, options) => { + capturedSignal = options.signal; + return new Promise(() => {}); + }, + }, + }, + }; + + await assert.rejects( + synthesizeSpeechBuffer(client, 'hello', { timeoutMs: 10 }), + (error) => error.code === 'VOICE_PROVIDER_TIMEOUT', + ); + assert.ok(capturedSignal); + assert.equal(capturedSignal.aborted, true); +}); + +test('voice session interruption aborts old turn work and reset creates a fresh signal', async () => { + const session = new VoiceLiveSession({ + id: 'session-1', + userId: 1, + sink: {}, + voiceSettings: {}, + }); + const firstSignal = session.signal; + + await session.interruptOutput(); + assert.equal(firstSignal.aborted, true); + assert.equal(firstSignal.reason.code, 'VOICE_INTERRUPTED'); + + session.resetTurnState(); + assert.notEqual(session.signal, firstSignal); + assert.equal(session.signal.aborted, false); + assert.equal(session.interrupted, false); +}); + +test('voice runtime shutdown closes sessions, aborts runs, and refuses new sessions', async () => { + const ctx = createTestRuntime(); + const { VoiceRuntimeManager } = require('../../../server/services/voice/runtimeManager'); + const abortedRuns = []; + const manager = new VoiceRuntimeManager({ + io: null, + agentEngine: { + abort(runId, options) { + abortedRuns.push({ runId, options }); + }, + }, + memoryManager: null, + }); + let sessionClosed = false; + let adapterClosed = false; + manager.sessions.set('session-1', { + id: 'session-1', + userId: 7, + currentRunId: 'run-1', + async close() { + sessionClosed = true; + }, + adapter: { + async close() { + adapterClosed = true; + }, + }, + }); + + try { + const status = await manager.shutdown(); + assert.equal(status.state, 'stopped'); + assert.equal(sessionClosed, true); + assert.equal(adapterClosed, true); + assert.deepEqual(abortedRuns, [{ + runId: 'run-1', + options: { userId: 7, reason: 'voice_session_closed' }, + }]); + assert.equal(manager.getSession('session-1'), null); + await assert.rejects( + manager.openSession({ userId: 7, sink: {} }), + (error) => error.code === 'VOICE_RUNTIME_SHUTDOWN', + ); + } finally { + teardownTestRuntime(ctx); + } +}); diff --git a/test/backend/unit/wearable_firmware_manifest.test.js b/test/backend/unit/wearable_firmware_manifest.test.js index a9d48130..89679d79 100644 --- a/test/backend/unit/wearable_firmware_manifest.test.js +++ b/test/backend/unit/wearable_firmware_manifest.test.js @@ -62,7 +62,7 @@ test('GitHub firmware manifest exposes the release binary and checksum', async ( if (String(url).includes('/releases?')) { return { ok: true, - json: async () => [firmwareRelease], + text: async () => JSON.stringify([firmwareRelease]), }; } if (url === 'https://downloads.example/neoagent-wearable-firmware.bin.sha256') { @@ -89,3 +89,47 @@ test('GitHub firmware manifest exposes the release binary and checksum', async ( 'https://github.com/NeoLabs-Systems/NeoAgent/releases/download/v4.0.0/neoagent-wearable-firmware.bin', ); }); + +test('firmware release lookup preserves caller cancellation', async () => { + const controller = new AbortController(); + const reason = new Error('manifest request stopped'); + let capturedSignal = null; + const fetchImpl = (_url, options) => { + capturedSignal = options.signal; + return new Promise((resolve, reject) => { + options.signal.addEventListener('abort', () => reject(options.signal.reason), { + once: true, + }); + }); + }; + + const pending = resolveFirmwareManifest({ + channel: 'stable', + repositoryOverride: 'Example/NeoAgentFirmwareAbortTest', + fetchImpl, + signal: controller.signal, + }); + await new Promise((resolve) => setImmediate(resolve)); + assert.ok(capturedSignal); + assert.notEqual(capturedSignal, controller.signal); + + controller.abort(reason); + await assert.rejects(pending, (error) => error === reason); + assert.equal(capturedSignal.aborted, true); +}); + +test('firmware release lookup rejects oversized GitHub responses safely', async () => { + const fetchImpl = async () => new Response('', { + status: 200, + headers: { 'content-length': String(3 * 1024 * 1024) }, + }); + + const manifest = await resolveFirmwareManifest({ + channel: 'stable', + repositoryOverride: 'Example/NeoAgentFirmwareOversizeTest', + fetchImpl, + }); + + assert.equal(manifest.configured, false); + assert.match(manifest.error, /response exceeded the .* safety limit/i); +}); diff --git a/test/flutter/unit/desktop_companion_actions_test.dart b/test/flutter/unit/desktop_companion_actions_test.dart new file mode 100644 index 00000000..4195156c --- /dev/null +++ b/test/flutter/unit/desktop_companion_actions_test.dart @@ -0,0 +1,82 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:neoagent_flutter/src/desktop_companion_actions.dart'; +import 'package:neoagent_flutter/src/desktop_screen_capture.dart'; + +class _UnsupportedScreenCapture implements DesktopScreenCapture { + @override + bool get isSupported => false; + + @override + Future captureCurrentScreen() async => null; +} + +void main() { + test('desktop display selection validates IDs and resolves primary', () { + final displays = >[ + {'id': 'left', 'primary': false}, + {'id': 'main', 'primary': true}, + ]; + + expect(resolveDesktopDisplaySelection(displays, 'primary'), 'main'); + expect(resolveDesktopDisplaySelection(displays, 'left'), 'left'); + expect( + resolveDesktopDisplaySelection( + >[ + {'id': 'left'}, + {'id': 'right'}, + ], + 'primary', + activeDisplayId: 'right', + ), + 'right', + ); + expect( + () => resolveDesktopDisplaySelection(displays, 'missing'), + throwsArgumentError, + ); + expect( + () => resolveDesktopDisplaySelection(const [], 'main'), + throwsStateError, + ); + }); + + test( + 'desktop shell command captures output and reports PTY truthfully', + () async { + final actions = DesktopCompanionActions( + screenCapture: _UnsupportedScreenCapture(), + ); + + final result = await actions.executeShellCommand( + commandId: 'quick-command', + command: Platform.isWindows ? 'echo ready' : 'printf ready', + requestedPty: true, + ); + + expect(result['exitCode'], 0); + expect(result['stdout'], 'ready'); + expect(result['ptyRequested'], isTrue); + expect(result['ptyAllocated'], isFalse); + }, + ); + + test('desktop shell cancellation terminates the tracked process', () async { + final actions = DesktopCompanionActions( + screenCapture: _UnsupportedScreenCapture(), + ); + final running = actions.executeShellCommand( + commandId: 'cancel-command', + command: Platform.isWindows ? 'ping -n 20 127.0.0.1 >NUL' : 'sleep 20', + ); + await Future.delayed(const Duration(milliseconds: 100)); + + final cancellation = await actions.cancelShellCommand('cancel-command'); + final result = await running.timeout(const Duration(seconds: 5)); + + expect(cancellation['cancelled'], isTrue); + expect(result['cancelled'], isTrue); + expect(result['killed'], isTrue); + }); +} diff --git a/test/flutter/unit/model_meta_test.dart b/test/flutter/unit/model_meta_test.dart new file mode 100644 index 00000000..9f90b013 --- /dev/null +++ b/test/flutter/unit/model_meta_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:neoagent_flutter/main.dart'; + +void main() { + test( + 'ModelMeta keeps selection identity separate from provider model id', + () { + final model = ModelMeta.fromJson({ + 'id': 'openai::gpt-5.3', + 'modelId': 'gpt-5.3', + 'label': 'GPT-5.3 (OpenAI)', + 'provider': 'openai', + 'purpose': 'general', + }); + + expect(model.id, 'openai::gpt-5.3'); + expect(model.modelId, 'gpt-5.3'); + }, + ); + + test('ModelMeta accepts catalogs from older servers', () { + final model = ModelMeta.fromJson({ + 'id': 'gpt-5.3', + 'label': 'GPT-5.3', + 'provider': 'openai', + 'purpose': 'general', + }); + + expect(model.id, 'gpt-5.3'); + expect(model.modelId, 'gpt-5.3'); + }); +}