From 250d67deba3385c2209c6e85c9f734b963036080 Mon Sep 17 00:00:00 2001 From: David Vitor Antonio Date: Wed, 10 Dec 2025 11:56:48 -0300 Subject: [PATCH 1/2] feat(plugins/hitl): assign agent from first message if unassigned (#14660) --- plugins/hitl/plugin.definition.ts | 2 +- .../before-incoming-event/hitl-assigned.ts | 31 ++-------- .../src/hooks/before-incoming-message/all.ts | 11 ++++ plugins/hitl/src/hooks/operations.ts | 58 +++++++++++++++++++ 4 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 plugins/hitl/src/hooks/operations.ts diff --git a/plugins/hitl/plugin.definition.ts b/plugins/hitl/plugin.definition.ts index 27b95f94193..2fc633753d7 100644 --- a/plugins/hitl/plugin.definition.ts +++ b/plugins/hitl/plugin.definition.ts @@ -102,7 +102,7 @@ const PLUGIN_CONFIG_SCHEMA = sdk.z.object({ export default new sdk.PluginDefinition({ name: 'hitl', - version: '1.2.0', + version: '1.3.0', title: 'Human In The Loop', description: 'Seamlessly transfer conversations to human agents', icon: 'icon.svg', diff --git a/plugins/hitl/src/hooks/before-incoming-event/hitl-assigned.ts b/plugins/hitl/src/hooks/before-incoming-event/hitl-assigned.ts index 3cb01b18387..d35fbf29463 100644 --- a/plugins/hitl/src/hooks/before-incoming-event/hitl-assigned.ts +++ b/plugins/hitl/src/hooks/before-incoming-event/hitl-assigned.ts @@ -1,8 +1,6 @@ -import { DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE } from '../../../plugin.definition' -import * as configuration from '../../configuration' import * as conv from '../../conv-manager' -import { tryLinkWebchatUser } from '../../webchat' import * as consts from '../consts' +import { assignAgent } from '../operations' import * as bp from '.botpress' export const handleEvent: bp.HookHandlers['before_incoming_event']['hitl:hitlAssigned'] = async (props) => { @@ -16,30 +14,11 @@ export const handleEvent: bp.HookHandlers['before_incoming_event']['hitl:hitlAss return consts.STOP_EVENT_HANDLING } - const upstreamConversationId = downstreamConversation.tags.upstream - if (!upstreamConversationId) { - props.logger - .withConversationId(downstreamConversationId) - .error('Downstream conversation was not binded to upstream conversation') - return consts.STOP_EVENT_HANDLING - } - - const upstreamConversation = await props.conversations.hitl.hitl.getById({ id: upstreamConversationId }) - const upstreamCm = conv.ConversationManager.from(props, upstreamConversation) - - const sessionConfig = await configuration.retrieveSessionConfig({ - ...props, - upstreamConversationId, + await assignAgent({ + props, + downstreamConversation, + humanAgentUserId, }) - const humanAgentUser = await props.users.getById({ id: humanAgentUserId }) - const humanAgentName = humanAgentUser?.name?.length ? humanAgentUser.name : 'A Human Agent' - - await Promise.all([ - upstreamCm.maybeRespondText(sessionConfig.onHumanAgentAssignedMessage, DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE), - downstreamCm.setHumanAgent(humanAgentUserId, humanAgentName), - upstreamCm.setHumanAgent(humanAgentUserId, humanAgentName), - tryLinkWebchatUser(props, { downstreamUser: humanAgentUser, upstreamConversation, forceLink: true }), - ]) return consts.STOP_EVENT_HANDLING } diff --git a/plugins/hitl/src/hooks/before-incoming-message/all.ts b/plugins/hitl/src/hooks/before-incoming-message/all.ts index 63fe3a31196..ba188aa2bbd 100644 --- a/plugins/hitl/src/hooks/before-incoming-message/all.ts +++ b/plugins/hitl/src/hooks/before-incoming-message/all.ts @@ -5,6 +5,7 @@ import { DEFAULT_USER_HITL_CLOSE_COMMAND, DEFAULT_USER_HITL_COMMAND_MESSAGE, } from 'plugin.definition' +import { assignAgent } from 'src/hooks/operations' import { tryLinkWebchatUser } from 'src/webchat' import * as configuration from '../../configuration' import * as conv from '../../conv-manager' @@ -70,6 +71,16 @@ const _handleDownstreamMessage = async ( props.logger.withConversationId(downstreamConversation.id).info('Sending message to upstream') const upstreamUserId = await tryLinkWebchatUser(props, { downstreamUser, upstreamConversation }) + + if (!downstreamConversation.tags.humanAgentId?.length) { + // Try to assing here, if there is no human agent assigned to the downstream conversation + await assignAgent({ + props, + downstreamConversation, + humanAgentUserId: downstreamUser.id, + }) + } + await upstreamCm.respond({ ...messagePayload, userId: upstreamUserId }) return consts.STOP_EVENT_HANDLING } diff --git a/plugins/hitl/src/hooks/operations.ts b/plugins/hitl/src/hooks/operations.ts new file mode 100644 index 00000000000..a5e4e701ad7 --- /dev/null +++ b/plugins/hitl/src/hooks/operations.ts @@ -0,0 +1,58 @@ +import { DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE } from '../../plugin.definition' +import * as configuration from '../configuration' +import * as conv from '../conv-manager' +import * as types from '../types' +import { tryLinkWebchatUser } from '../webchat' +import * as bp from '.botpress' + +type AssignAgentProps = bp.HookHandlerProps['before_incoming_message'] | bp.HookHandlerProps['before_incoming_event'] + +export type AssignAgentOptions = { + props: AssignAgentProps + downstreamConversation: types.ActionableConversation + humanAgentUserId: string + forceLinkWebchatUser?: boolean +} + +/** + * Assigns a human agent to both downstream and upstream conversations. + * Also links the webchat user if applicable and sends the agent assigned message. + */ +export const assignAgent = async (options: AssignAgentOptions): Promise => { + const { props, downstreamConversation, humanAgentUserId, forceLinkWebchatUser = true } = options + + const upstreamConversationId = downstreamConversation.tags.upstream + if (!upstreamConversationId?.length) { + props.logger + .withConversationId(downstreamConversation.id) + .error('Downstream conversation was not binded to upstream conversation') + return false + } + + const upstreamConversation = await props.conversations.hitl.hitl.getById({ + id: upstreamConversationId, + }) + + const upstreamCm = conv.ConversationManager.from(props, upstreamConversation) + const downstreamCm = conv.ConversationManager.from(props, downstreamConversation) + const humanAgentUser = await props.users.getById({ id: humanAgentUserId }) + const humanAgentName = humanAgentUser?.name?.length ? humanAgentUser.name : 'A Human Agent' + + const sessionConfig = await configuration.retrieveSessionConfig({ + ...props, + upstreamConversationId: upstreamConversation.id, + }) + + await Promise.all([ + downstreamCm.setHumanAgent(humanAgentUserId, humanAgentName), + upstreamCm.setHumanAgent(humanAgentUserId, humanAgentName), + upstreamCm.maybeRespondText(sessionConfig.onHumanAgentAssignedMessage, DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE), + tryLinkWebchatUser(props, { + downstreamUser: humanAgentUser, + upstreamConversation, + forceLink: forceLinkWebchatUser, + }), + ]) + + return true +} From d6ee02ac7fc2ad62b959c3e4527b224f3c7056b5 Mon Sep 17 00:00:00 2001 From: David Vitor Antonio Date: Wed, 10 Dec 2025 12:08:45 -0300 Subject: [PATCH 2/2] feat(zendesk-messaging-hitl): add integration (#14606) --- .../deploy-integrations-production.yml | 2 +- .../workflows/deploy-integrations-staging.yml | 1 + .../zendesk-messaging-hitl/.gitignore | 1 + .../zendesk-messaging-hitl/eslint.config.mjs | 13 + integrations/zendesk-messaging-hitl/hub.md | 56 ++ integrations/zendesk-messaging-hitl/icon.svg | 8 + .../integration.definition.ts | 147 +++++ .../zendesk-messaging-hitl/package.json | 26 + .../src/actions/hitl.ts | 240 ++++++++ .../src/actions/index.ts | 8 + .../zendesk-messaging-hitl/src/channels.ts | 169 ++++++ .../zendesk-messaging-hitl/src/client.ts | 558 +++++++++++++++++ .../src/events/conversation-message.ts | 70 +++ .../src/events/index.ts | 2 + .../src/events/switchboard-release-control.ts | 38 ++ .../zendesk-messaging-hitl/src/handler.ts | 61 ++ .../zendesk-messaging-hitl/src/index.ts | 21 + .../zendesk-messaging-hitl/src/setup/index.ts | 7 + .../src/setup/register.ts | 60 ++ .../src/setup/unregister.ts | 29 + .../zendesk-messaging-hitl/src/setup/util.ts | 98 +++ .../src/sunshine-api.ts | 565 ++++++++++++++++++ .../src/sunshine-events.ts | 112 ++++ .../zendesk-messaging-hitl/src/types.ts | 29 + .../zendesk-messaging-hitl/src/util.ts | 31 + .../zendesk-messaging-hitl/tsconfig.json | 8 + pnpm-lock.yaml | 357 ++++++----- 27 files changed, 2550 insertions(+), 167 deletions(-) create mode 100644 integrations/zendesk-messaging-hitl/.gitignore create mode 100644 integrations/zendesk-messaging-hitl/eslint.config.mjs create mode 100644 integrations/zendesk-messaging-hitl/hub.md create mode 100644 integrations/zendesk-messaging-hitl/icon.svg create mode 100644 integrations/zendesk-messaging-hitl/integration.definition.ts create mode 100644 integrations/zendesk-messaging-hitl/package.json create mode 100644 integrations/zendesk-messaging-hitl/src/actions/hitl.ts create mode 100644 integrations/zendesk-messaging-hitl/src/actions/index.ts create mode 100644 integrations/zendesk-messaging-hitl/src/channels.ts create mode 100644 integrations/zendesk-messaging-hitl/src/client.ts create mode 100644 integrations/zendesk-messaging-hitl/src/events/conversation-message.ts create mode 100644 integrations/zendesk-messaging-hitl/src/events/index.ts create mode 100644 integrations/zendesk-messaging-hitl/src/events/switchboard-release-control.ts create mode 100644 integrations/zendesk-messaging-hitl/src/handler.ts create mode 100644 integrations/zendesk-messaging-hitl/src/index.ts create mode 100644 integrations/zendesk-messaging-hitl/src/setup/index.ts create mode 100644 integrations/zendesk-messaging-hitl/src/setup/register.ts create mode 100644 integrations/zendesk-messaging-hitl/src/setup/unregister.ts create mode 100644 integrations/zendesk-messaging-hitl/src/setup/util.ts create mode 100644 integrations/zendesk-messaging-hitl/src/sunshine-api.ts create mode 100644 integrations/zendesk-messaging-hitl/src/sunshine-events.ts create mode 100644 integrations/zendesk-messaging-hitl/src/types.ts create mode 100644 integrations/zendesk-messaging-hitl/src/util.ts create mode 100644 integrations/zendesk-messaging-hitl/tsconfig.json diff --git a/.github/workflows/deploy-integrations-production.yml b/.github/workflows/deploy-integrations-production.yml index e052a12ec60..54a79372592 100644 --- a/.github/workflows/deploy-integrations-production.yml +++ b/.github/workflows/deploy-integrations-production.yml @@ -31,7 +31,7 @@ jobs: uses: ./.github/actions/deploy-integrations with: environment: 'production' - extra_filter: "-F '!docusign'" + extra_filter: "-F '!docusign' -F '!zendesk-messaging-hitl'" force: ${{ github.event.inputs.force == 'true' }} sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} token_cloud_ops_account: ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }} diff --git a/.github/workflows/deploy-integrations-staging.yml b/.github/workflows/deploy-integrations-staging.yml index 2ac13f8316f..c453a4217fc 100644 --- a/.github/workflows/deploy-integrations-staging.yml +++ b/.github/workflows/deploy-integrations-staging.yml @@ -39,6 +39,7 @@ jobs: uses: ./.github/actions/deploy-integrations with: environment: 'staging' + extra_filter: "-F '!zendesk-messaging-hitl'" force: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force == 'true' }} dry_run: ${{ github.event_name == 'pull_request' }} sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} diff --git a/integrations/zendesk-messaging-hitl/.gitignore b/integrations/zendesk-messaging-hitl/.gitignore new file mode 100644 index 00000000000..87e6b2c246d --- /dev/null +++ b/integrations/zendesk-messaging-hitl/.gitignore @@ -0,0 +1 @@ +.botpress diff --git a/integrations/zendesk-messaging-hitl/eslint.config.mjs b/integrations/zendesk-messaging-hitl/eslint.config.mjs new file mode 100644 index 00000000000..8c81907e7de --- /dev/null +++ b/integrations/zendesk-messaging-hitl/eslint.config.mjs @@ -0,0 +1,13 @@ +import rootConfig from '../../eslint.config.mjs' + +export default [ + ...rootConfig, + { + languageOptions: { + parserOptions: { + project: ['./tsconfig.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + }, +] diff --git a/integrations/zendesk-messaging-hitl/hub.md b/integrations/zendesk-messaging-hitl/hub.md new file mode 100644 index 00000000000..e9f19107a3a --- /dev/null +++ b/integrations/zendesk-messaging-hitl/hub.md @@ -0,0 +1,56 @@ +# Zendesk Messaging HITL + +## Description + +This integration makes Sunshine Conversations (Sunco) available as a channel for Human-in-the-loop on Botpress, integrated with Zendesk. When a user requests to speak with an agent, a conversation will be created in Sunshine Conversations and passed to Zendesk Agent Workspace for agent handling. + +## Configuration + +You will need to have access to your **Zendesk Account** account to configure this integration. + +### Configuration fields on Botpress + +- **App ID**: Your Sunshine Conversations App ID +- **Key ID**: Your Sunshine Conversations Key ID +- **Key Secret**: Your Sunshine Conversations Key Secret + +### Automatic Setup + +When you register this integration, it will automatically: + +1. Create a Integration in Zendesk Account with the name `botpress-hitl-{webhookId}` +2. Configure the webhook to receive events from Zendesk Messaging +3. Set up the integration to handle messages and switchboard events + +The webhook URL is automatically configured during registration + +## Enabling Multi Conversations + +Please enable multi conversations in Zendesk Messaging, you need to configure your Zendesk account settings: + +1. Go to your Zendesk Admin Center +2. Navigate to **Channels** > **Messaging and social** > **Manage Settings** +3. Enable **Multi conversations** + +When multi conversations is enabled, each new HITL session will create a separate conversation in Zendesk, allowing for better organization and handling of multiple support requests from the same customer. + +## Ending Sessions + +### Overview + +Ending messaging sessions allows you to stop further interaction via messaging without closing tickets. This is useful to end a hitl session so the user can get back to the bot. + +Please allow the agent to end sessions for a better hitl experience, otherwise, only the user will be able to do it + +1. Go to your Zendesk Admin Center +2. Navigate to **Channels** > **Messaging and social** > **Manage Settings** +3. Enable **Ending messaging sessions** + +### Agent Actions + +Agents can end messaging sessions at any time through the Zendesk Agent Workspace interface. When a session is ended: + +1. The messaging channel for that conversation is closed and user is sent back to the bot +2. The ticket remains open for email follow-up. +3. A new hitl sessions from the customer will create a new ticket +4. The agent's capacity is automatically released diff --git a/integrations/zendesk-messaging-hitl/icon.svg b/integrations/zendesk-messaging-hitl/icon.svg new file mode 100644 index 00000000000..46d77351d38 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/icon.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/integrations/zendesk-messaging-hitl/integration.definition.ts b/integrations/zendesk-messaging-hitl/integration.definition.ts new file mode 100644 index 00000000000..244c7ef1b7b --- /dev/null +++ b/integrations/zendesk-messaging-hitl/integration.definition.ts @@ -0,0 +1,147 @@ +import * as sdk from '@botpress/sdk' +import { sentry as sentryHelpers } from '@botpress/sdk-addons' +import hitl from './bp_modules/hitl' + +export default new sdk.IntegrationDefinition({ + name: 'zendesk-messaging-hitl', + version: '0.1.0', + title: 'Zendesk Messaging HITL', + description: 'This integration allows your bot to use Sunshine Conversations (Sunco) as a HITL Provider for Zendesk', + icon: 'icon.svg', + readme: 'hub.md', + configuration: { + schema: sdk.z.object({ + appId: sdk.z.string().min(1).title('App ID').describe('Your Sunshine Conversations App ID'), + keyId: sdk.z.string().min(1).title('Key ID').describe('Your Sunshine Conversations Key ID'), + keySecret: sdk.z.string().min(1).title('Key Secret').describe('Your Sunshine Conversations Key Secret'), + }), + }, + states: { + switchboardIntegrationIds: { + type: 'integration', + schema: sdk.z.object({ + switchboardIntegrationId: sdk.z + .string() + .title('Switchboard Integration ID') + .describe('The ID of the Botpress switchboard integration used for HITL sessions') + .optional(), + agentWorkspaceSwitchboardIntegrationId: sdk.z + .string() + .title('Agent Workspace Switchboard Integration ID') + .describe('The ID of the Zendesk Agent Workspace switchboard integration') + .optional(), + }), + }, + }, + channels: {}, + events: {}, + user: { + tags: { + id: { + title: 'User ID', + description: 'The Sunshine Conversations user ID', + }, + email: { + title: 'Email', + description: 'The email address of the Sunshine Conversations user', + }, + }, + }, + entities: { + hitlConversation: { + title: 'HITL Conversation', + description: 'A support request', + schema: sdk.z.object({ + priority: sdk.z + .enum(['Low', 'Medium', 'High', 'Urgent']) + .title('Priority') + .describe('Priority of the conversation. Leave empty for default priority.') + .optional(), + originSourceType: sdk.z + .enum([ + 'android', + 'ios', + 'web', + 'sdk', + 'apple', + 'googlercs', + 'instagram', + 'kakao', + 'line', + 'mailgun', + 'messagebird', + 'messenger', + 'slackconnect', + 'telegram', + 'twilio', + 'twitter', + 'viber', + 'wechat', + 'whatsapp', + ]) + .title('Origin Source Type') + .describe( + 'The channel where this conversation originated from. Leave empty to default to SUNSHINE CONVERSATIONS API.' + ) + .optional(), + organizationId: sdk.z + .string() + .title('Organization Id') + .describe('The organization ID to assign the ticket to.') + .optional(), + brandId: sdk.z.string().title('Brand Id').describe('The brand ID to assign the ticket to.').optional(), + groupId: sdk.z.string().title('Group Id').describe('The group ID to assign the ticket to.').optional(), + assigneeId: sdk.z.string().title('Assignee Id').describe('The assignee ID to assign the ticket to.').optional(), + tags: sdk.z.array(sdk.z.string()).title('Tags').describe('Tags to add to the ticket.').optional(), + customTicketFields: sdk.z + .array( + sdk.z.object({ + id: sdk.z.string().min(1).title('Custom Field ID').describe('The ID of the custom field in Zendesk'), + value: sdk.z + .string() + .min(1) + .title('Custom Field Value') + .describe('The value to set for this custom field'), + }) + ) + .title('Custom Ticket Fields') + .describe( + 'Custom ticket fields to set on the ticket. The id should be the ticket field ID, and the value should be the field value. Example: { "40033266756891": "value" }' + ) + .optional(), + additionalMetadata: sdk.z + .array( + sdk.z.object({ + key: sdk.z.string().min(1).title('Metadata Key').describe('The metadata key to add'), + value: sdk.z.string().min(1).title('Metadata Value').describe('The value to set for this metadata key'), + }) + ) + .title('Additional Metadata') + .describe('Additional metadata fields to add directly to the metadata object.') + .optional(), + }), + }, + }, + secrets: sentryHelpers.COMMON_SECRET_NAMES, +}).extend(hitl, (self) => ({ + entities: { hitlSession: self.entities.hitlConversation }, + channels: { + hitl: { + title: 'Zendesk Messaging', + description: 'Zendesk Messaging HITL', + conversation: { + tags: { + id: { title: 'Sunco Conversation Id', description: 'Sunco Conversation Id' }, + }, + }, + message: { + tags: { + id: { + title: 'Sunco Message ID', + description: 'The ID of the message in Sunco', + }, + }, + }, + }, + }, +})) diff --git a/integrations/zendesk-messaging-hitl/package.json b/integrations/zendesk-messaging-hitl/package.json new file mode 100644 index 00000000000..0096822304d --- /dev/null +++ b/integrations/zendesk-messaging-hitl/package.json @@ -0,0 +1,26 @@ +{ + "name": "@botpresshub/zendesk-messaging-hitl", + "description": "Zendesk Messaging HITL integration for Botpress", + "private": true, + "scripts": { + "build": "bp add -y && bp build", + "check:type": "tsc --noEmit", + "check:bplint": "bp lint" + }, + "dependencies": { + "@botpress/cli": "workspace:*", + "@botpress/client": "workspace:*", + "@botpress/common": "workspace:*", + "@botpress/sdk": "workspace:*", + "@botpress/sdk-addons": "workspace:*", + "sunshine-conversations-client": "^9.12.0" + }, + "devDependencies": { + "@botpress/cli": "workspace:*", + "@botpress/common": "workspace:*", + "@sentry/cli": "^2.39.1" + }, + "bpDependencies": { + "hitl": "../../interfaces/hitl" + } +} diff --git a/integrations/zendesk-messaging-hitl/src/actions/hitl.ts b/integrations/zendesk-messaging-hitl/src/actions/hitl.ts new file mode 100644 index 00000000000..0410e3f48e4 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/actions/hitl.ts @@ -0,0 +1,240 @@ +import { RuntimeError } from '@botpress/client' +import { buildConversationTranscript } from '@botpress/common' +import { getSuncoClient } from 'src/client' +import { getSwitchboardIntegrationId, getAgentWorkspaceSwitchboardIntegrationId } from 'src/setup/util' +import { Client, IntegrationCtx, User, HitlSession, MessageHistory } from 'src/types' +import * as bp from '.botpress' + +export const startHitl: bp.IntegrationProps['actions']['startHitl'] = async ({ ctx, client, input, logger }) => { + try { + const suncoClient = getSuncoClient(ctx.configuration) + + const { user } = await client.getUser({ + id: input.userId, + }) + + const suncoUserId = user.tags.id + if (!suncoUserId?.length) { + throw new RuntimeError("Input user doesn't have a Sunco User Id") + } + + const { title, description, messageHistory } = input + + const suncoConversation = await suncoClient.createConversation({ + userId: suncoUserId, + }) + + const { conversation } = await client.getOrCreateConversation({ + channel: 'hitl', + tags: { + id: suncoConversation.id, + }, + }) + + // Get switchboard integration IDs (from cache or fetch at runtime) + const switchboardIntegrationId = await getSwitchboardIntegrationId(ctx, client, logger) + const agentWorkspaceSwitchboardIntegrationId = await getAgentWorkspaceSwitchboardIntegrationId(ctx, client, logger) + + // Pass control from conversation to our Integration in the switchboard, necessary so the initial message can be sent without creating the ticket yet + // Reason: If the ticket is created because of the initial message, we can't specify ticket metadata anymore (ticket fields, priority, etc.) + await suncoClient.switchboardActionsPassControl(suncoConversation.id, switchboardIntegrationId) + + // Send a initial message with the conversation title, description and transcript + // Having a message will allow us to pass control to the agent workspace without requiring a 'reason' + await suncoClient.sendMessages( + suncoConversation.id, + { displayName: 'HITL Session' }, + await _buildInitialMessages({ + ctx, + client, + user, + title, + description, + messageHistory, + }) + ) + + const metadata = _buildMetadata(input.hitlSession, user) + + logger + .forBot() + .info('Passing control to the agent workspace with the following metadata: ' + JSON.stringify({ metadata })) + + // Pass control to the agent workspace with the metadata to correctly create fields + await suncoClient.switchboardActionsPassControl( + suncoConversation.id, + agentWorkspaceSwitchboardIntegrationId, + metadata + ) + + // Offer control to our integration so our webhook can receive messages from the agent workspace, even if we don't control it + // In theory we could skip this if deliverStandbyEvents was enabled on our Switchboard Integration record but this way we prevent + // all bots that use this HITL integration from receiving webhook events from all conversations + await suncoClient.switchboardActionsOfferControl(suncoConversation.id, switchboardIntegrationId) + + return { + conversationId: conversation.id, + } + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + throw new RuntimeError(errMsg) + } +} + +export const stopHitl: bp.IntegrationProps['actions']['stopHitl'] = async ({ input, client, ctx, logger }) => { + const { conversation } = await client.getConversation({ + id: input.conversationId, + }) + + const suncoConversationId: string | undefined = conversation.tags.id + + if (!suncoConversationId) { + logger.forBot().warn('No Sunco conversation ID found, cannot release control') + return {} + } + + try { + const suncoClient = getSuncoClient(ctx.configuration) + + logger.forBot().info(`Releasing control from switchboard for conversation ${suncoConversationId}`) + await suncoClient.switchboardActionsReleaseControl(suncoConversationId, 'ticketClosed') + logger.forBot().info(`HITL conversation ${suncoConversationId} stopped and control released`) + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + logger.forBot().error(`Failed to release control for conversation ${suncoConversationId}: ${errMsg}`) + } + + return {} +} + +export const createUser: bp.IntegrationProps['actions']['createUser'] = async ({ client, input, ctx, logger }) => { + logger.forBot().info('createUser called', { input }) + + try { + const suncoClient = getSuncoClient(ctx.configuration) + + const { user: botpressUser } = await client.getOrCreateUser({ + ...input, + tags: { + email: input.email, + }, + }) + + const suncoUser = await suncoClient.getOrCreateUser({ + ...input, + externalId: botpressUser.id, + }) + + if (!suncoUser.id) { + throw new RuntimeError('Failed to create Sunco User') + } + + await client.updateUser({ + ...input, + id: botpressUser.id, + tags: { + id: suncoUser.id, + }, + }) + + return { + userId: botpressUser.id, + } + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + throw new RuntimeError(errMsg) + } +} + +function _buildMetadata(hitlSession?: HitlSession, user?: User): Record { + const metadata: Record = {} + + if (user?.name?.length) { + metadata['dataCapture.systemField.requester.name'] = user.name + } + + if (user?.tags?.email?.length) { + metadata['dataCapture.systemField.requester.email'] = user.tags.email + } + + if (hitlSession?.priority?.length) { + metadata['dataCapture.systemField.priority'] = hitlSession.priority + } + + if (hitlSession?.originSourceType?.length) { + metadata.origin_source_type = hitlSession.originSourceType + } + + if (hitlSession?.organizationId?.length) { + metadata['dataCapture.systemField.organization_id'] = hitlSession.organizationId + } + + if (hitlSession?.brandId?.length) { + metadata['dataCapture.systemField.brand_id'] = hitlSession.brandId + } + + if (hitlSession?.groupId?.length) { + metadata['dataCapture.systemField.group_id'] = hitlSession.groupId + } + + if (hitlSession?.assigneeId?.length) { + metadata['dataCapture.systemField.assignee_id'] = hitlSession.assigneeId + } + + if (hitlSession?.tags && hitlSession.tags.length > 0) { + metadata['dataCapture.systemField.tags'] = hitlSession.tags.join(',') + } + + if (hitlSession?.customTicketFields?.length) { + for (const field of hitlSession.customTicketFields) { + if (field.id?.length && field.value?.length) { + metadata[`dataCapture.ticketField.${field.id}`] = field.value + } + } + } + + if (hitlSession?.additionalMetadata?.length) { + for (const field of hitlSession.additionalMetadata) { + if (field.key?.length && field.value?.length) { + metadata[field.key] = field.value + } + } + } + + return metadata +} + +async function _buildInitialMessages(args: { + ctx: IntegrationCtx + client: Client + user?: User + title?: string + description?: string + messageHistory: MessageHistory +}): Promise> { + const { ctx, client, user, title, description, messageHistory } = args + + const transcript = await buildConversationTranscript({ + ctx, + client, + messages: messageHistory, + customTranscriptFormatter: (msgs) => + msgs.map((msg) => (msg.isBot ? 'Bot: ' : 'User: ') + msg.text.join('\n')).join('\n\n'), + }) + + return [ + { + type: 'text' as const, + text: `New Conversation Started + +Email: ${user?.tags?.email || 'No email provided'} +Title: ${title || 'Untitled'} +Description: ${description || 'No description provided'} + `, + }, + { + type: 'text' as const, + text: 'Transcript:\n\n' + transcript, + }, + ] +} diff --git a/integrations/zendesk-messaging-hitl/src/actions/index.ts b/integrations/zendesk-messaging-hitl/src/actions/index.ts new file mode 100644 index 00000000000..05c865407bc --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/actions/index.ts @@ -0,0 +1,8 @@ +import { startHitl, stopHitl, createUser } from './hitl' +import * as bp from '.botpress' + +export const actions = { + startHitl, + stopHitl, + createUser, +} satisfies bp.IntegrationProps['actions'] diff --git a/integrations/zendesk-messaging-hitl/src/channels.ts b/integrations/zendesk-messaging-hitl/src/channels.ts new file mode 100644 index 00000000000..be35cb00823 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/channels.ts @@ -0,0 +1,169 @@ +import * as bpCommon from '@botpress/common' +import * as sdk from '@botpress/sdk' +import { getSuncoClient } from './client' +import { getMediaMetadata } from './util' +import * as bp from '.botpress' + +const wrapChannel = bpCommon.createChannelWrapper()({ + toolFactories: { + suncoClient({ ctx }) { + return getSuncoClient(ctx.configuration) + }, + + async suncoUserId({ client, payload, user: attachedUser }) { + const user = payload.userId ? (await client.getUser({ id: payload.userId })).user : attachedUser + return user.tags.id + }, + + async suncoConversationId({ conversation }) { + const suncoConversationId = conversation.tags.id + + if (!suncoConversationId) { + throw new sdk.RuntimeError('Sunco conversation id not found') + } + + return suncoConversationId + }, + }, +}) + +export const channels = { + hitl: { + messages: { + text: wrapChannel( + { channelName: 'hitl', messageType: 'text' }, + async ({ ack, payload, suncoClient, suncoUserId, suncoConversationId }) => { + const suncoMessage = await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'text', text: payload.text }, + ]) + await ack({ + tags: { id: suncoMessage.id }, + }) + } + ), + + image: wrapChannel( + { channelName: 'hitl', messageType: 'image' }, + async ({ ack, payload, suncoClient, suncoUserId, suncoConversationId }) => { + const suncoMessage = await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'image', mediaUrl: payload.imageUrl }, + ]) + await ack({ + tags: { id: suncoMessage.id }, + }) + } + ), + + audio: wrapChannel( + { channelName: 'hitl', messageType: 'audio' }, + async ({ ack, payload, suncoClient, suncoUserId, suncoConversationId }) => { + const metadata = await getMediaMetadata(payload.audioUrl) + const suncoMessage = await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'file', mediaUrl: payload.audioUrl, mediaType: metadata.mimeType }, + ]) + + await ack({ + tags: { id: suncoMessage.id }, + }) + } + ), + + video: wrapChannel( + { channelName: 'hitl', messageType: 'video' }, + async ({ ack, payload, suncoClient, suncoUserId, suncoConversationId }) => { + const metadata = await getMediaMetadata(payload.videoUrl) + const suncoMessage = await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'file', mediaUrl: payload.videoUrl, mediaType: metadata.mimeType }, + ]) + await ack({ + tags: { id: suncoMessage.id }, + }) + } + ), + + file: wrapChannel( + { channelName: 'hitl', messageType: 'file' }, + async ({ ack, payload, suncoClient, suncoUserId, suncoConversationId }) => { + const metadata = await getMediaMetadata(payload.fileUrl) + const suncoMessage = await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { + type: 'file', + mediaUrl: payload.fileUrl, + mediaType: metadata.mimeType, + }, + ]) + await ack({ + tags: { id: suncoMessage.id }, + }) + } + ), + + bloc: wrapChannel( + { channelName: 'hitl', messageType: 'bloc' }, + async ({ ack, payload, suncoClient, suncoUserId, suncoConversationId }) => { + for (const item of payload.items) { + switch (item.type) { + case 'text': + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'text', text: item.payload.text }, + ]) + break + case 'markdown': + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'text', text: item.payload.markdown }, + ]) + break + case 'image': + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'image', mediaUrl: item.payload.imageUrl }, + ]) + break + case 'video': + const videoMetadata = await getMediaMetadata(item.payload.videoUrl) + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'file', mediaUrl: item.payload.videoUrl, mediaType: videoMetadata.mimeType }, + ]) + break + case 'audio': + const audioMetadata = await getMediaMetadata(item.payload.audioUrl) + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'file', mediaUrl: item.payload.audioUrl, mediaType: audioMetadata.mimeType }, + ]) + break + case 'file': + const fileMetadata = await getMediaMetadata(item.payload.fileUrl) + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { + type: 'file', + mediaUrl: item.payload.fileUrl, + mediaType: fileMetadata.mimeType, + }, + ]) + break + case 'location': + const { title, address, latitude, longitude } = item.payload + const messageParts = [] + + if (title) { + messageParts.push(title, '') + } + if (address) { + messageParts.push(address, '') + } + messageParts.push(`Latitude: ${latitude}`, `Longitude: ${longitude}`) + + await suncoClient.sendMessages(suncoConversationId, suncoUserId, [ + { type: 'text', text: messageParts.join('\n') }, + ]) + break + default: + item satisfies never + } + } + + await ack({ tags: {} }) + } + ), + }, + }, +} satisfies bp.IntegrationProps['channels'] diff --git a/integrations/zendesk-messaging-hitl/src/client.ts b/integrations/zendesk-messaging-hitl/src/client.ts new file mode 100644 index 00000000000..75095b4218e --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/client.ts @@ -0,0 +1,558 @@ +import { RuntimeError } from '@botpress/client' +import { + SunshineConversationsApi, + type AppsApi, + type UsersApi, + type ConversationsApi, + type MessagesApi, + type WebhooksApi, + type IntegrationsApi, + type SwitchboardsApi, + type SwitchboardActionsApi, + type SwitchboardIntegrationsApi, + type PostMessageRequest, + type MessageAuthor, + type MessageContent, +} from './sunshine-api' + +export class SuncoClientError extends RuntimeError { + public readonly operationName: string + public readonly status?: number + public readonly data?: unknown + public readonly requestBody?: unknown + public readonly additionalContext?: Record + + public constructor( + message: string, + operationName: string, + options?: { + status?: number + data?: unknown + requestBody?: unknown + additionalContext?: Record + } + ) { + const details: string[] = [message] + if (options?.status) { + details.push(`Status: ${options.status}`) + } + if (options?.data) { + details.push(`Data: ${JSON.stringify(options.data)}`) + } + if (options?.requestBody) { + details.push(`Request Body: ${JSON.stringify(options.requestBody)}`) + } + if (options?.additionalContext) { + const contextStr = Object.entries(options.additionalContext) + .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) + .join(', ') + if (contextStr) { + details.push(`Context: ${contextStr}`) + } + } + super(`Failed to ${operationName}: ${details.join(' | ')}`) + this.name = 'SuncoClientError' + this.operationName = operationName + this.status = options?.status + this.data = options?.data + this.requestBody = options?.requestBody + this.additionalContext = options?.additionalContext + } +} + +class SuncoClient { + private _appId: string + private _client: { + apps: AppsApi + users: UsersApi + conversations: ConversationsApi + messages: MessagesApi + webhooks: WebhooksApi + integrations: IntegrationsApi + switchboard: SwitchboardsApi + switchboardActions: SwitchboardActionsApi + switchboardIntegrations: SwitchboardIntegrationsApi + } + + public constructor(config: { appId: string; keyId: string; keySecret: string }) { + this._appId = config.appId + const apiClient = new SunshineConversationsApi.ApiClient() + const auth = apiClient.authentications['basicAuth'] + auth.username = config.keyId + auth.password = config.keySecret + + this._client = { + apps: new SunshineConversationsApi.AppsApi(apiClient), + users: new SunshineConversationsApi.UsersApi(apiClient), + conversations: new SunshineConversationsApi.ConversationsApi(apiClient), + messages: new SunshineConversationsApi.MessagesApi(apiClient), + webhooks: new SunshineConversationsApi.WebhooksApi(apiClient), + integrations: new SunshineConversationsApi.IntegrationsApi(apiClient), + switchboard: new SunshineConversationsApi.SwitchboardsApi(apiClient), + switchboardActions: new SunshineConversationsApi.SwitchboardActionsApi(apiClient), + switchboardIntegrations: new SunshineConversationsApi.SwitchboardIntegrationsApi(apiClient), + } + } + + private _isNetworkError(error: unknown): error is { + status?: number + body?: any + request?: { + data?: unknown + body?: unknown + } + response?: { + status?: number + text?: string + req: { + method: string + url: string + headers: Record + data?: unknown + body?: unknown + } + header: Record + } + } { + return typeof error === 'object' && error !== null && 'status' in error + } + + private _getNetworkErrorDetails(error: unknown): + | { + message: string + status?: number + data?: unknown + requestBody?: unknown + } + | undefined { + if (typeof error !== 'object' || error === null) { + return undefined + } + + if (!this._isNetworkError(error)) { + return undefined + } + + // Parse error message from various formats + let message: string | undefined + + // Check for Sunshine Conversations API error format (errors array in body) + if (Array.isArray(error.body?.errors)) { + const errorMessages = (error.body.errors as Array<{ title?: string; code?: string }>) + .map((err) => { + if (err.title) { + return err.code ? `${err.title}: ${err.code}` : err.title + } + return JSON.stringify(err) + }) + .filter((msg): msg is string => msg !== undefined) + + if (errorMessages.length > 0) { + message = errorMessages.join('; ') + } + } else if (error.body?.message?.length) { + message = error.body?.message + } else if (error.body) { + message = JSON.stringify(error.body) + } + + const requestBody = + error.request?.data ?? error.request?.body ?? error.response?.req?.data ?? error.response?.req?.body + + return { + message: message ?? 'Unknown error', + status: error.status ?? error.response?.status, + data: error.body, + requestBody, + } + } + + private _handleError(thrown: unknown, operationName: string, additionalContext?: Record): never { + if (thrown instanceof SuncoClientError) { + throw thrown + } + + const networkErrorDetails = this._getNetworkErrorDetails(thrown) + const errorMessage = networkErrorDetails?.message || String(thrown) + + throw new SuncoClientError(errorMessage, operationName, { + status: networkErrorDetails?.status, + data: networkErrorDetails?.data, + requestBody: networkErrorDetails?.requestBody, + additionalContext, + }) + } + + public async getApp() { + try { + const result = await this._client.apps.getApp(this._appId) + + return result.app + } catch (thrown: unknown) { + this._handleError(thrown, 'get app', { appId: this._appId }) + } + } + + public async getOrCreateUser(props: { name?: string; email?: string; avatarUrl?: string; externalId: string }) { + try { + const existingUser = await this.getUserByIdOrExternalId(props.externalId) + if (existingUser) { + return existingUser + } + + return await this.createUser(props) + } catch (thrown: unknown) { + this._handleError(thrown, 'get or create user', props) + } + } + + public async getUserByIdOrExternalId(userIdOrExternalId: string) { + try { + const result = await this._client.users.getUser(this._appId, userIdOrExternalId) + + return result.user + } catch (thrown: unknown) { + const networkError = this._getNetworkErrorDetails(thrown) + if (networkError && networkError.status === 404) { + return null + } + + this._handleError(thrown, 'get user by ID or external ID', { userIdOrExternalId }) + } + } + + public async createUser(props: { name?: string; email?: string; avatarUrl?: string; externalId: string }) { + const { name, email, avatarUrl, externalId } = props + + const nameParts = name?.split(' ') || [] + const givenName = nameParts[0] || '' + const surname = nameParts.slice(1).join(' ') || '' + + try { + const result = await this._client.users.createUser(this._appId, { + externalId, + profile: { + ...(givenName.length && { givenName }), + ...(surname.length && { surname }), + ...(email && { email }), + ...(avatarUrl && { avatarUrl }), + }, + }) + + return result.user + } catch (thrown: unknown) { + this._handleError(thrown, 'create user') + } + } + + public async createConversation(args: { userId: string }) { + try { + const result = await this._client.conversations.createConversation(this._appId, { + type: 'personal', + participants: [ + { + userId: args.userId, + subscribeSDKClient: false, + }, + ], + displayName: 'HITL Conversation', + }) + + if (!result?.conversation?.id) { + throw new RuntimeError('Conversation creation succeeded but no ID returned') + } + + return result.conversation + } catch (thrown: unknown) { + this._handleError(thrown, 'create conversation') + } + } + + public async sendMessages( + conversationId: string, + userIdOrAuthor: + | string + | undefined + | { + type?: 'user' | 'business' + subtypes?: string[] + displayName?: string + avatarUrl?: string + }, + messageParts: Array + ) { + try { + let message + + let author: MessageAuthor + if (typeof userIdOrAuthor === 'string') { + // userId provided + author = { + type: 'user', + userId: userIdOrAuthor, + } + } else if (userIdOrAuthor && typeof userIdOrAuthor === 'object') { + // Author object provided - default type to 'business' if not specified + author = { + type: userIdOrAuthor.type || 'business', + ...(userIdOrAuthor.subtypes && { subtypes: userIdOrAuthor.subtypes }), + ...(userIdOrAuthor.displayName && { displayName: userIdOrAuthor.displayName }), + ...(userIdOrAuthor.avatarUrl && { avatarUrl: userIdOrAuthor.avatarUrl }), + } + } else { + // No userId or author, default to business + author = { + type: 'business', + } + } + + for (const part of messageParts) { + const messagePost: PostMessageRequest = { + author, + content: part, + } + + const result = await this._client.messages.postMessage(this._appId, conversationId, messagePost) + if (result.messages && result.messages.length > 0) { + const firstMessage = result.messages[0] + if (firstMessage?.id) { + message = firstMessage + } + } + } + + if (!message) { + throw new RuntimeError('Failed to send message') + } + + return message + } catch (thrown: unknown) { + this._handleError(thrown, 'send message', { conversationId, messageParts }) + } + } + + public async createIntegration(integrationName: string, webhookUrl: string) { + try { + const result = await this._client.integrations.createIntegration(this._appId, { + type: 'custom', + status: 'active', + displayName: integrationName, + webhooks: [ + { + target: webhookUrl, + triggers: [ + 'conversation:message', + 'conversation:remove', + 'conversation:join', + 'conversation:postback', + 'switchboard:releaseControl', + ], + includeFullUser: false, + includeFullSource: false, + }, + ], + }) + + if (!result?.integration?.id) { + throw new RuntimeError('Integration creation succeeded but no ID returned') + } + + return result.integration + } catch (thrown: unknown) { + this._handleError(thrown, 'create integration') + } + } + + public async createWebhook(integrationId: string, webhookUrl: string) { + try { + return this._client.webhooks.createIntegrationWebhook(this._appId, integrationId, { + target: webhookUrl, + triggers: ['conversation:message', 'conversation:read'], + includeFullUser: false, + includeFullSource: false, + }) + } catch (thrown: unknown) { + this._handleError(thrown, 'create webhook', { integrationId }) + } + } + + public async deleteWebhook(integrationId: string, webhookId: string) { + try { + await this._client.webhooks.deleteIntegrationWebhook(this._appId, integrationId, webhookId) + } catch (thrown: unknown) { + this._handleError(thrown, 'delete webhook', { integrationId, webhookId }) + } + } + + public async deleteIntegration(integrationId: string) { + try { + await this._client.integrations.deleteIntegration(this._appId, integrationId) + } catch (thrown: unknown) { + this._handleError(thrown, 'delete integration', { integrationId }) + } + } + + public async findIntegrationByDisplayNameOrThrow(displayName: string) { + try { + const integrations = (await this._client.integrations.listIntegrations(this._appId)).integrations || [] + const integration = integrations.find((int) => int.displayName === displayName) + if (!integration) { + throw new RuntimeError('Integration not found') + } + + return integration + } catch (thrown: unknown) { + this._handleError(thrown, 'find integration by name', { displayName }) + } + } + + public async findSwitchboardIntegrationByNameOrThrow(switchboardId: string, name: string) { + try { + const result = await this._client.switchboardIntegrations.listSwitchboardIntegrations(this._appId, switchboardId) + const switchboardIntegrations = result.switchboardIntegrations || [] + const switchboardIntegration = switchboardIntegrations.find( + (si: { name?: string; id?: string }) => si.name === name + ) + if (!switchboardIntegration) { + throw new RuntimeError(`Switchboard integration with name "${name}" not found`) + } + + return switchboardIntegration + } catch (thrown: unknown) { + this._handleError(thrown, 'find switchboard integration by name', { switchboardId, name }) + } + } + + public async findAgentWorkspaceIntegrationOrThrow(switchboardId: string) { + try { + const result = await this._client.switchboardIntegrations.listSwitchboardIntegrations(this._appId, switchboardId) + const switchboardIntegrations = result.switchboardIntegrations || [] + const agentWorkspaceIntegration = switchboardIntegrations.find( + (si: { integrationType?: string; id?: string }) => si.integrationType === 'zd:agentWorkspace' + ) + if (!agentWorkspaceIntegration || !agentWorkspaceIntegration.id) { + throw new RuntimeError('No agent workspace integration found with integrationType zd:agentWorkspace') + } + + return agentWorkspaceIntegration + } catch (thrown: unknown) { + this._handleError(thrown, 'find agent workspace integration', { switchboardId }) + } + } + + public async switchboardActionsPassControl( + conversationId: string, + switchboardIntegrationId: string, + metadata?: Record + ) { + try { + await this._client.switchboardActions.passControl(this._appId, conversationId, { + switchboardIntegration: switchboardIntegrationId, + metadata: metadata || {}, + }) + } catch (thrown: unknown) { + this._handleError(thrown, 'pass control to switchboard', { + conversationId, + switchboardIntegrationId, + metadata, + }) + } + } + + public async switchboardActionsOfferControl( + conversationId: string, + switchboardIntegrationId: string, + metadata?: Record + ) { + try { + await this._client.switchboardActions.offerControl(this._appId, conversationId, { + switchboardIntegration: switchboardIntegrationId, + metadata: metadata || {}, + }) + } catch (thrown: unknown) { + this._handleError(thrown, 'offer control to switchboard', { + conversationId, + switchboardIntegrationId, + metadata, + }) + } + } + + public async switchboardActionsReleaseControl(conversationId: string, reason?: string) { + try { + await this._client.switchboardActions.releaseControl(this._appId, conversationId, { + ...(reason && { reason }), + }) + } catch (thrown: unknown) { + this._handleError(thrown, 'release control from switchboard', { + conversationId, + reason, + }) + } + } + + public async getSwitchboardOrThrow() { + try { + const switchboards = (await this._client.switchboard.listSwitchboards(this._appId)).switchboards || [] + if (switchboards.length === 0) { + throw new RuntimeError('No switchboards found') + } else if (switchboards.length > 1) { + throw new RuntimeError('Multiple switchboards found') + } + + const firstSwitchboard = switchboards[0] + if (!firstSwitchboard) { + throw new RuntimeError('No switchboards available') + } + + return firstSwitchboard + } catch (thrown: unknown) { + this._handleError(thrown, 'get switchboard ID') + } + } + + public async createSwitchboardIntegration( + switchboardId: string, + integrationId: string, + name: string, + deliverStandbyEvents: boolean = false + ) { + try { + const result = await this._client.switchboardIntegrations.createSwitchboardIntegration( + this._appId, + switchboardId, + { + name, // Required: Identifier for use in control transfer protocols + integrationId, // Required for custom integrations + deliverStandbyEvents, // Optional: defaults to false + } + ) + + return result.switchboardIntegration + } catch (thrown: unknown) { + this._handleError(thrown, 'create switchboard integration', { + switchboardId, + integrationId, + name, + }) + } + } + + public async deleteSwitchboardIntegration(switchboardId: string, switchboardIntegrationId: string) { + try { + await this._client.switchboardIntegrations.deleteSwitchboardIntegration( + this._appId, + switchboardId, + switchboardIntegrationId + ) + } catch (thrown: unknown) { + this._handleError(thrown, 'delete switchboard integration', { + switchboardId, + switchboardIntegrationId, + }) + } + } +} + +export const getSuncoClient = (config: { appId: string; keyId: string; keySecret: string }) => new SuncoClient(config) +export type { SuncoClient } diff --git a/integrations/zendesk-messaging-hitl/src/events/conversation-message.ts b/integrations/zendesk-messaging-hitl/src/events/conversation-message.ts new file mode 100644 index 00000000000..1dcbb47a3e8 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/events/conversation-message.ts @@ -0,0 +1,70 @@ +import { ConversationMessageEvent } from 'src/sunshine-events' +import { Client, Conversation, CreateMessageInputPayload, CreateMessageInputType, Logger } from 'src/types' + +export async function handleConversationMessage( + event: ConversationMessageEvent, + conversation: Conversation, + client: Client, + logger: Logger +): Promise { + const payload = event.payload + + // Agent messages will come as business + if (payload.message.author.type !== 'business') { + return + } + + const zendeskAgentId: string | undefined = payload.message?.metadata?.['__zendesk_msg.agent.id'] + + if (!zendeskAgentId?.length) { + return + } + + const { user } = await client.getOrCreateUser({ + tags: { + id: zendeskAgentId, + }, + name: payload.message.author.displayName, + pictureUrl: payload.message.author.avatarUrl, + }) + + const messageContent = payload.message.content + + const createMessage = async (type: CreateMessageInputType, messagePayload: CreateMessageInputPayload) => { + await client.createMessage({ + tags: { id: payload.message.id }, + type, + userId: user.id, + conversationId: conversation.id, + payload: messagePayload, + }) + } + + switch (messageContent.type) { + case 'text': + if (!messageContent.text?.length) { + logger.forBot().warn('Text message received but no text provided') + return + } + + await createMessage('text', { text: messageContent.text }) + break + case 'image': + await createMessage('image', { imageUrl: messageContent.mediaUrl }) + break + case 'file': + const mediaType = messageContent.mediaType || '' + const mediaUrl = messageContent.mediaUrl + + if (mediaType.startsWith('video/')) { + await createMessage('video', { videoUrl: mediaUrl }) + } else if (mediaType.startsWith('audio/')) { + await createMessage('audio', { audioUrl: mediaUrl }) + } else { + await createMessage('file', { fileUrl: mediaUrl, title: messageContent.altText }) + } + break + default: + logger.forBot().warn(`Received a message with unsupported content type: ${messageContent.type}`) + } +} diff --git a/integrations/zendesk-messaging-hitl/src/events/index.ts b/integrations/zendesk-messaging-hitl/src/events/index.ts new file mode 100644 index 00000000000..50a73d7835e --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/events/index.ts @@ -0,0 +1,2 @@ +export { handleConversationMessage } from './conversation-message' +export { handleSwitchboardReleaseControl } from './switchboard-release-control' diff --git a/integrations/zendesk-messaging-hitl/src/events/switchboard-release-control.ts b/integrations/zendesk-messaging-hitl/src/events/switchboard-release-control.ts new file mode 100644 index 00000000000..09f0b733979 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/events/switchboard-release-control.ts @@ -0,0 +1,38 @@ +import { Client, Conversation, Logger } from 'src/types' +import { SwitchboardReleaseControlEvent } from '../sunshine-events' + +export async function handleSwitchboardReleaseControl( + event: SwitchboardReleaseControlEvent, + conversation: Conversation, + client: Client, + logger: Logger +): Promise { + const payload = event.payload + const suncoConversationId = payload.conversation?.id + + if (!suncoConversationId) { + logger.forBot().warn('switchboard:releaseControl event missing conversation ID') + return + } + + logger + .forBot() + .info( + `Received switchboard:releaseControl event for conversation ${suncoConversationId}, reason: ${payload.reason}` + ) + + try { + // Emit hitlStopped event to close the HITL session + await client.createEvent({ + type: 'hitlStopped', + payload: { + conversationId: conversation.id, + }, + }) + + logger.forBot().info(`HITL session stopped for conversation ${conversation.id} due to switchboard releaseControl`) + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + logger.forBot().error(`Failed to handle switchboard:releaseControl event: ${errMsg}`) + } +} diff --git a/integrations/zendesk-messaging-hitl/src/handler.ts b/integrations/zendesk-messaging-hitl/src/handler.ts new file mode 100644 index 00000000000..21e7e8cb5b7 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/handler.ts @@ -0,0 +1,61 @@ +import { handleConversationMessage, handleSwitchboardReleaseControl } from './events' +import { isSuncoWebhookPayload } from './sunshine-events' +import { Handler } from './types' + +export const handler: Handler = async ({ req, logger, client }) => { + if (!req.body) { + logger.forBot().warn('Handler received an empty body') + return + } + + try { + const data: unknown = JSON.parse(req.body) + + if (!isSuncoWebhookPayload(data)) { + logger.forBot().warn('Received an invalid payload from Sunco') + return + } + + for (const event of data.events) { + const suncoConversationId = event.payload.conversation?.id + + if (!suncoConversationId) { + logger.forBot().warn('Event missing conversation ID, skipping') + continue + } + + const conversation = ( + await client.listConversations({ + channel: 'hitl', + tags: { + id: suncoConversationId, + }, + }) + )?.conversations[0] + + if (!conversation) { + logger + .forBot() + .warn( + `Ignoring Sunshine conversation ${suncoConversationId} because it was not created by the startHitl action` + ) + continue + } + + const eventType = event.type + switch (eventType) { + case 'switchboard:releaseControl': + await handleSwitchboardReleaseControl(event, conversation, client, logger) + break + case 'conversation:message': + await handleConversationMessage(event, conversation, client, logger) + break + default: + logger.forBot().debug(`Unhandled event type: ${eventType}`) + } + } + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + logger.forBot().error(`Error processing Sunco webhook: ${errMsg}`) + } +} diff --git a/integrations/zendesk-messaging-hitl/src/index.ts b/integrations/zendesk-messaging-hitl/src/index.ts new file mode 100644 index 00000000000..03f3959ffa1 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/index.ts @@ -0,0 +1,21 @@ +import { sentry as sentryHelpers } from '@botpress/sdk-addons' +import { actions } from './actions' +import { channels } from './channels' +import { handler } from './handler' +import { register, unregister } from './setup' +import * as bp from '.botpress' + +export default sentryHelpers.wrapIntegration( + new bp.Integration({ + register, + unregister, + actions, + channels, + handler, + }), + { + dsn: bp.secrets.SENTRY_DSN, + environment: bp.secrets.SENTRY_ENVIRONMENT, + release: bp.secrets.SENTRY_RELEASE, + } +) diff --git a/integrations/zendesk-messaging-hitl/src/setup/index.ts b/integrations/zendesk-messaging-hitl/src/setup/index.ts new file mode 100644 index 00000000000..2eb2fb12cca --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/setup/index.ts @@ -0,0 +1,7 @@ +export { register } from './register' +export { unregister } from './unregister' +export { + getBotpressIntegrationDisplayName, + getSwitchboardIntegrationId, + getAgentWorkspaceSwitchboardIntegrationId, +} from './util' diff --git a/integrations/zendesk-messaging-hitl/src/setup/register.ts b/integrations/zendesk-messaging-hitl/src/setup/register.ts new file mode 100644 index 00000000000..939379c33e8 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/setup/register.ts @@ -0,0 +1,60 @@ +import { RuntimeError } from '@botpress/client' +import * as bp from '../../.botpress' +import { getSuncoClient } from '../client' +import { getBotpressIntegrationDisplayName, fetchAndCacheSwitchboardIntegrationsIdOrThrow } from './util' + +export const register: bp.IntegrationProps['register'] = async ({ ctx, webhookUrl, logger, client }) => { + try { + logger.forBot().info('Starting Zendesk Messaging HITL integration registration...') + + const suncoClient = getSuncoClient(ctx.configuration) + + logger.forBot().info('Verifying credentials...') + try { + const app = await suncoClient.getApp() + logger.forBot().info('✅ Credentials verified successfully. App details:', JSON.stringify(app, null, 2)) + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + throw new RuntimeError(`Invalid credentials: ${errMsg}`) + } + + const integrationDisplayName = getBotpressIntegrationDisplayName(ctx.webhookId) + logger.forBot().info(`Integration Display name: ${integrationDisplayName}`) + + logger.forBot().info('Checking for existing integration by name...') + let integrationId: string + try { + integrationId = (await suncoClient.findIntegrationByDisplayNameOrThrow(integrationDisplayName)).id + } catch { + logger.forBot().info(`No existing integration found. Creating new integration with webhook ${webhookUrl}`) + integrationId = (await suncoClient.createIntegration(integrationDisplayName, webhookUrl)).id + logger.forBot().info(`✅ Integration created successfully with ID: ${integrationId}`) + } + + // Get the switchboard ID, in theory there should only be one (we can't create and there is already a default one) + const { id: switchboardId } = await suncoClient.getSwitchboardOrThrow() + + logger.forBot().info('Checking for existing switchboard integration...') + let switchboardIntegrationId: string + try { + switchboardIntegrationId = ( + await suncoClient.findSwitchboardIntegrationByNameOrThrow(switchboardId, integrationDisplayName) + ).id + } catch { + logger.forBot().info('No switchboard integration found. Creating new switchboard integration...') + switchboardIntegrationId = ( + await suncoClient.createSwitchboardIntegration(switchboardId, integrationId, integrationDisplayName, false) + ).id + logger.forBot().info(`✅ Switchboard integration created successfully with ID: ${switchboardIntegrationId}`) + } + + // Force on register to allow bot devs to update cache on demand, in theory these will not change + logger.forBot().info('Fetching and caching switchboard integrations IDs...') + await fetchAndCacheSwitchboardIntegrationsIdOrThrow(ctx, client, logger) + + logger.forBot().info('✅ Zendesk Messaging HITL integration registered successfully') + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + throw new RuntimeError(`Failed to register Zendesk Messaging HITL integration: ${errMsg}`) + } +} diff --git a/integrations/zendesk-messaging-hitl/src/setup/unregister.ts b/integrations/zendesk-messaging-hitl/src/setup/unregister.ts new file mode 100644 index 00000000000..61900ee268b --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/setup/unregister.ts @@ -0,0 +1,29 @@ +import * as bp from '../../.botpress' +import { getSuncoClient } from '../client' +import { getBotpressIntegrationDisplayName } from './util' + +export const unregister: bp.IntegrationProps['unregister'] = async ({ ctx, logger }) => { + try { + const suncoClient = getSuncoClient(ctx.configuration) + + const { id: switchboardId } = await suncoClient.getSwitchboardOrThrow() + + const integrationDisplayName = getBotpressIntegrationDisplayName(ctx.webhookId) + + const { id: switchboardIntegrationId } = await suncoClient.findSwitchboardIntegrationByNameOrThrow( + switchboardId, + integrationDisplayName + ) + + await suncoClient.deleteSwitchboardIntegration(switchboardId, switchboardIntegrationId) + + const { id: integrationId } = await suncoClient.findIntegrationByDisplayNameOrThrow(integrationDisplayName) + + await suncoClient.deleteIntegration(integrationId) + + logger.forBot().info('Zendesk Messaging HITL integration unregistered') + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + logger.forBot().error(`Failed to unregister integration, skipping: ${errMsg}`) + } +} diff --git a/integrations/zendesk-messaging-hitl/src/setup/util.ts b/integrations/zendesk-messaging-hitl/src/setup/util.ts new file mode 100644 index 00000000000..6c156d8ebe3 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/setup/util.ts @@ -0,0 +1,98 @@ +import { RuntimeError } from '@botpress/sdk' +import { getSuncoClient } from '../client' +import { Client, IntegrationCtx, Logger } from '../types' + +export function getBotpressIntegrationDisplayName(webhookId: string): string { + return `botpress-hitl-${webhookId}` +} + +export async function fetchAndCacheSwitchboardIntegrationsIdOrThrow( + ctx: IntegrationCtx, + client: Client, + logger: Logger +): Promise<{ switchboardIntegrationId: string; agentWorkspaceSwitchboardIntegrationId: string }> { + logger.forBot().info('Fetching switchboard integration IDs from API...') + const suncoClient = getSuncoClient(ctx.configuration) + const integrationDisplayName = getBotpressIntegrationDisplayName(ctx.webhookId) + const { id: switchboardId } = await suncoClient.getSwitchboardOrThrow() + + // Find both switchboard integrations + const switchboardIntegration = await suncoClient.findSwitchboardIntegrationByNameOrThrow( + switchboardId, + integrationDisplayName + ) + const agentWorkspaceIntegration = await suncoClient.findAgentWorkspaceIntegrationOrThrow(switchboardId) + + // Cache both values + await client.getOrSetState({ + type: 'integration', + name: 'switchboardIntegrationIds', + id: ctx.integrationId, + payload: { + switchboardIntegrationId: switchboardIntegration.id, + agentWorkspaceSwitchboardIntegrationId: agentWorkspaceIntegration.id, + }, + }) + + logger + .forBot() + .info( + `✅ Fetched and cached switchboard integration IDs - Switchboard Integration ID: ${switchboardIntegration.id}, Agent Workspace Switchboard Integration ID: ${agentWorkspaceIntegration.id}` + ) + + return { + switchboardIntegrationId: switchboardIntegration.id, + agentWorkspaceSwitchboardIntegrationId: agentWorkspaceIntegration.id, + } +} + +export async function getSwitchboardIntegrationId( + ctx: IntegrationCtx, + client: Client, + logger: Logger +): Promise { + // Try to get from cache first + try { + const { switchboardIntegrationId } = ( + await client.getState({ + type: 'integration', + name: 'switchboardIntegrationIds', + id: ctx.integrationId, + }) + ).state.payload + + if (!switchboardIntegrationId?.length) { + throw new RuntimeError('No switchboard integration ID found in cache') + } + + return switchboardIntegrationId + } catch { + return (await fetchAndCacheSwitchboardIntegrationsIdOrThrow(ctx, client, logger)).switchboardIntegrationId + } +} + +export async function getAgentWorkspaceSwitchboardIntegrationId( + ctx: IntegrationCtx, + client: Client, + logger: Logger +): Promise { + // Try to get from cache first + try { + const { agentWorkspaceSwitchboardIntegrationId } = ( + await client.getState({ + type: 'integration', + name: 'switchboardIntegrationIds', + id: ctx.integrationId, + }) + ).state.payload + + if (!agentWorkspaceSwitchboardIntegrationId?.length) { + throw new RuntimeError('No agent workspace switchboard integration ID found in cache') + } + + return agentWorkspaceSwitchboardIntegrationId + } catch { + return (await fetchAndCacheSwitchboardIntegrationsIdOrThrow(ctx, client, logger)) + .agentWorkspaceSwitchboardIntegrationId + } +} diff --git a/integrations/zendesk-messaging-hitl/src/sunshine-api.ts b/integrations/zendesk-messaging-hitl/src/sunshine-api.ts new file mode 100644 index 00000000000..8d345f3ce80 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/sunshine-api.ts @@ -0,0 +1,565 @@ +// @ts-expect-error No types for sunshine-conversations-client +import * as SunshineConversationsClientModule from 'sunshine-conversations-client' + +// The typings below were generated using AI based on dist from sunshine-conversations-client + +// ============================================================================ +// ApiClient Types +// ============================================================================ + +export type BasicAuth = { + type: 'basic' + username?: string + password?: string +} + +export type BearerAuth = { + type: 'bearer' + accessToken?: string +} + +export type ApiClientAuthentications = { + basicAuth: BasicAuth + bearerAuth: BearerAuth +} + +// This is a very simplified type for the ApiClient +export type ApiClient = { + authentications: ApiClientAuthentications +} + +// ============================================================================ +// AppsApi Types +// ============================================================================ + +export type App = { + id: string + displayName?: string + subdomain?: string + settings?: unknown + metadata?: unknown +} + +export type CreateAppRequest = { + displayName?: string + settings?: unknown + metadata?: unknown +} + +export type UpdateAppRequest = { + displayName?: string + settings?: unknown + metadata?: unknown +} + +export type AppsApi = { + getApp(appId: string): Promise<{ app: App }> + createApp(appPost: CreateAppRequest): Promise<{ app: App }> + updateApp(appId: string, appUpdate: UpdateAppRequest): Promise<{ app: App }> + deleteApp(appId: string): Promise + listApps(): Promise<{ apps?: App[] }> +} + +// ============================================================================ +// UsersApi Types +// ============================================================================ + +export type UserProfile = { + givenName?: string + surname?: string + email?: string + avatarUrl?: string +} + +export type User = { + id: string + externalId?: string + profile?: UserProfile +} + +export type CreateUserRequest = { + externalId: string + profile?: UserProfile +} + +export type UpdateUserRequest = { + profile?: UserProfile +} + +export type UsersApi = { + getUser(appId: string, userIdOrExternalId: string): Promise<{ user: User }> + createUser(appId: string, userPost: CreateUserRequest): Promise<{ user: User }> + updateUser(appId: string, userId: string, userUpdate: UpdateUserRequest): Promise<{ user: User }> + deleteUser(appId: string, userId: string): Promise + listUsers(appId: string, page?: number, limit?: number): Promise<{ users?: User[] }> +} + +// ============================================================================ +// ConversationsApi Types +// ============================================================================ + +export type Conversation = { + id: string + type?: 'personal' | 'sdkGroup' + displayName?: string + activeSwitchboardIntegration?: { + id: string + name?: string + integrationId?: string + integrationType?: string + } + pendingSwitchboardIntegration?: { + id: string + name?: string + integrationId?: string + integrationType?: string + } +} + +export type ConversationParticipant = { + userId: string + subscribeSDKClient?: boolean +} + +export type CreateConversationRequest = { + type: 'personal' | 'sdkGroup' + displayName?: string + participants: ConversationParticipant[] + metadata?: Record +} + +export type UpdateConversationRequest = { + displayName?: string + metadata?: Record +} + +export type ConversationsApi = { + getConversation(appId: string, conversationId: string): Promise<{ conversation: Conversation }> + createConversation( + appId: string, + conversationPost: CreateConversationRequest + ): Promise<{ conversation: Conversation }> + updateConversation( + appId: string, + conversationId: string, + conversationUpdate: UpdateConversationRequest + ): Promise<{ conversation: Conversation }> + deleteConversation(appId: string, conversationId: string): Promise + listConversations(appId: string, page?: number, limit?: number): Promise<{ conversations?: Conversation[] }> +} + +// ============================================================================ +// MessagesApi Types +// ============================================================================ + +export type MessageAuthor = { + type?: 'user' | 'business' + userId?: string + displayName?: string + avatarUrl?: string + subtypes?: string[] +} + +export type TextMessageContent = { + type: 'text' + text: string +} + +export type ImageMessageContent = { + type: 'image' + mediaUrl: string + mediaType?: string + altText?: string +} + +export type FileMessageContent = { + type: 'file' + mediaUrl: string + mediaType?: string + altText?: string +} + +export type LocationMessageContent = { + type: 'location' + coordinates: { + lat: number + long: number + } +} + +export type CarouselMessageContent = { + type: 'carousel' + items: unknown[] +} + +export type ListMessageContent = { + type: 'list' + items: unknown[] +} + +export type MessageContent = + | TextMessageContent + | ImageMessageContent + | FileMessageContent + | LocationMessageContent + | CarouselMessageContent + | ListMessageContent + +export type Message = { + id: string + type?: string + author?: MessageAuthor + content?: MessageContent + received?: string + source?: { + type?: string + originalMessageTimestamp?: string + } + metadata?: Record +} + +export type PostMessageRequest = { + author: MessageAuthor + content: MessageContent + metadata?: Record +} + +export type MessagesApi = { + postMessage(appId: string, conversationId: string, messagePost: PostMessageRequest): Promise<{ messages?: Message[] }> + listMessages(appId: string, conversationId: string, page?: number, limit?: number): Promise<{ messages?: Message[] }> + deleteMessage(appId: string, conversationId: string, messageId: string): Promise + deleteAllMessages(appId: string, conversationId: string): Promise +} + +// ============================================================================ +// IntegrationsApi Types +// ============================================================================ + +export type Integration = { + id: string + type?: string + status?: 'active' | 'inactive' + displayName?: string + webhooks?: Webhook[] +} + +export type CreateIntegrationRequest = { + type: 'custom' | string + status?: 'active' | 'inactive' + displayName: string + webhooks?: CreateWebhookRequest[] +} + +export type UpdateIntegrationRequest = { + status?: 'active' | 'inactive' + displayName?: string +} + +export type IntegrationsApi = { + getIntegration(appId: string, integrationId: string): Promise<{ integration: Integration }> + createIntegration(appId: string, integrationPost: CreateIntegrationRequest): Promise<{ integration: Integration }> + updateIntegration( + appId: string, + integrationId: string, + integrationUpdate: UpdateIntegrationRequest + ): Promise<{ integration: Integration }> + deleteIntegration(appId: string, integrationId: string): Promise + listIntegrations(appId: string): Promise<{ integrations?: Integration[] }> +} + +// ============================================================================ +// WebhooksApi Types +// ============================================================================ + +export type Webhook = { + id: string + target: string + triggers?: string[] + includeFullUser?: boolean + includeFullSource?: boolean +} + +export type CreateWebhookRequest = { + target: string + triggers: string[] + includeFullUser?: boolean + includeFullSource?: boolean +} + +export type UpdateWebhookRequest = { + target?: string + triggers?: string[] + includeFullUser?: boolean + includeFullSource?: boolean +} + +export type WebhooksApi = { + getIntegrationWebhook(appId: string, integrationId: string, webhookId: string): Promise<{ webhook: Webhook }> + createIntegrationWebhook( + appId: string, + integrationId: string, + webhookPost: CreateWebhookRequest + ): Promise<{ webhook: Webhook }> + updateIntegrationWebhook( + appId: string, + integrationId: string, + webhookId: string, + webhookUpdate: UpdateWebhookRequest + ): Promise<{ webhook: Webhook }> + deleteIntegrationWebhook(appId: string, integrationId: string, webhookId: string): Promise + listIntegrationWebhooks(appId: string, integrationId: string): Promise<{ webhooks?: Webhook[] }> +} + +// ============================================================================ +// SwitchboardsApi Types +// ============================================================================ + +export type Switchboard = { + id: string + name?: string + integrationId?: string +} + +export type SwitchboardsApi = { + getSwitchboard(appId: string, switchboardId: string): Promise<{ switchboard: Switchboard }> + createSwitchboard(appId: string, switchboardPost: { name?: string }): Promise<{ switchboard: Switchboard }> + updateSwitchboard( + appId: string, + switchboardId: string, + switchboardUpdate: { name?: string } + ): Promise<{ switchboard: Switchboard }> + deleteSwitchboard(appId: string, switchboardId: string): Promise + listSwitchboards(appId: string): Promise<{ switchboards?: Switchboard[] }> +} + +// ============================================================================ +// SwitchboardIntegrationsApi Types +// ============================================================================ + +export type SwitchboardIntegration = { + id: string + name?: string + integrationId?: string + integrationType?: string + deliverStandbyEvents?: boolean +} + +export type CreateSwitchboardIntegrationRequest = { + name: string + integrationId: string + deliverStandbyEvents?: boolean +} + +export type UpdateSwitchboardIntegrationRequest = { + name?: string + deliverStandbyEvents?: boolean +} + +export type SwitchboardIntegrationsApi = { + getSwitchboardIntegration( + appId: string, + switchboardId: string, + switchboardIntegrationId: string + ): Promise<{ switchboardIntegration: SwitchboardIntegration }> + createSwitchboardIntegration( + appId: string, + switchboardId: string, + switchboardIntegrationPost: CreateSwitchboardIntegrationRequest + ): Promise<{ switchboardIntegration: SwitchboardIntegration }> + updateSwitchboardIntegration( + appId: string, + switchboardId: string, + switchboardIntegrationId: string, + switchboardIntegrationUpdate: UpdateSwitchboardIntegrationRequest + ): Promise<{ switchboardIntegration: SwitchboardIntegration }> + deleteSwitchboardIntegration(appId: string, switchboardId: string, switchboardIntegrationId: string): Promise + listSwitchboardIntegrations( + appId: string, + switchboardId: string + ): Promise<{ switchboardIntegrations?: SwitchboardIntegration[] }> +} + +// ============================================================================ +// SwitchboardActionsApi Types +// ============================================================================ + +export type PassControlRequest = { + switchboardIntegration: string + metadata?: Record +} + +export type OfferControlRequest = { + switchboardIntegration: string + metadata?: Record +} + +export type ReleaseControlRequest = { + reason?: string +} + +export type SwitchboardActionsApi = { + passControl(appId: string, conversationId: string, passControlBody: PassControlRequest): Promise + offerControl(appId: string, conversationId: string, offerControlBody: OfferControlRequest): Promise + releaseControl(appId: string, conversationId: string, releaseControlBody: ReleaseControlRequest): Promise +} + +// ============================================================================ +// ClientsApi Types +// ============================================================================ + +export type Client = { + id: string + type?: string + userId?: string +} + +export type ClientsApi = { + getClient(appId: string, userId: string, clientId: string): Promise<{ client: Client }> + createClient(appId: string, userId: string, clientPost: { type?: string }): Promise<{ client: Client }> + deleteClient(appId: string, userId: string, clientId: string): Promise + listClients(appId: string, userId: string): Promise<{ clients?: Client[] }> +} + +// ============================================================================ +// ParticipantsApi Types +// ============================================================================ + +export type Participant = { + id: string + userId?: string + joinDate?: string +} + +export type ParticipantsApi = { + addParticipant( + appId: string, + conversationId: string, + participantPost: { userId: string; subscribeSDKClient?: boolean } + ): Promise<{ participant: Participant }> + removeParticipant(appId: string, conversationId: string, participantId: string): Promise + listParticipants(appId: string, conversationId: string): Promise<{ participants?: Participant[] }> +} + +// ============================================================================ +// AttachmentsApi Types +// ============================================================================ + +export type Attachment = { + id: string + mediaUrl?: string + mediaType?: string + mediaSize?: number +} + +export type AttachmentsApi = { + getAttachment(appId: string, attachmentId: string): Promise<{ attachment: Attachment }> + uploadAttachment( + appId: string, + attachmentPost: { source?: string; access?: string } + ): Promise<{ attachment: Attachment }> + deleteAttachment(appId: string, attachmentId: string): Promise +} + +// ============================================================================ +// ActivitiesApi Types +// ============================================================================ + +export type Activity = { + id: string + type?: string + author?: { + type?: string + userId?: string + } +} + +export type ActivitiesApi = { + listActivities( + appId: string, + conversationId: string, + page?: number, + limit?: number + ): Promise<{ activities?: Activity[] }> +} + +// ============================================================================ +// AppKeysApi Types +// ============================================================================ + +export type AppKey = { + id: string + key?: string + secret?: string +} + +export type AppKeysApi = { + createAppKey(appId: string, appKeyPost: { displayName?: string }): Promise<{ key: AppKey }> + deleteAppKey(appId: string, keyId: string): Promise + listAppKeys(appId: string): Promise<{ keys?: AppKey[] }> +} + +// ============================================================================ +// CustomIntegrationApiKeysApi Types +// ============================================================================ + +export type CustomIntegrationApiKey = { + id: string + key?: string + secret?: string +} + +export type CustomIntegrationApiKeysApi = { + createCustomIntegrationApiKey( + appId: string, + integrationId: string, + customIntegrationApiKeyPost: { displayName?: string } + ): Promise<{ key: CustomIntegrationApiKey }> + deleteCustomIntegrationApiKey(appId: string, integrationId: string, keyId: string): Promise + listCustomIntegrationApiKeys(appId: string, integrationId: string): Promise<{ keys?: CustomIntegrationApiKey[] }> +} + +// ============================================================================ +// OAuthEndpointsApi Types +// ============================================================================ + +export type OAuthEndpoint = { + id: string + uri?: string +} + +export type OAuthEndpointsApi = { + createOAuthEndpoint( + appId: string, + integrationId: string, + oAuthEndpointPost: { uri: string } + ): Promise<{ oauthEndpoint: OAuthEndpoint }> + deleteOAuthEndpoint(appId: string, integrationId: string, oauthEndpointId: string): Promise + listOAuthEndpoints(appId: string, integrationId: string): Promise<{ oauthEndpoints?: OAuthEndpoint[] }> +} + +// ============================================================================ +// SunshineConversationsApi Export +// ============================================================================ + +// Helper type to create a constructor that returns an instance implementing the interface +type TypedApiConstructor = new (apiClient: ApiClient) => T + +export const SunshineConversationsApi = SunshineConversationsClientModule as { + ApiClient: new () => ApiClient + ActivitiesApi: TypedApiConstructor + AppKeysApi: TypedApiConstructor + AppsApi: TypedApiConstructor + AttachmentsApi: TypedApiConstructor + ClientsApi: TypedApiConstructor + ConversationsApi: TypedApiConstructor + CustomIntegrationApiKeysApi: TypedApiConstructor + IntegrationsApi: TypedApiConstructor + MessagesApi: TypedApiConstructor + OAuthEndpointsApi: TypedApiConstructor + ParticipantsApi: TypedApiConstructor + SwitchboardActionsApi: TypedApiConstructor + SwitchboardIntegrationsApi: TypedApiConstructor + SwitchboardsApi: TypedApiConstructor + UsersApi: TypedApiConstructor + WebhooksApi: TypedApiConstructor +} diff --git a/integrations/zendesk-messaging-hitl/src/sunshine-events.ts b/integrations/zendesk-messaging-hitl/src/sunshine-events.ts new file mode 100644 index 00000000000..e49143b4c18 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/sunshine-events.ts @@ -0,0 +1,112 @@ +import { z } from '@botpress/sdk' + +//https://developer.zendesk.com/api-reference/conversations/#tag/Webhooks/operation/EventWebhooks +export type SuncoWebhookPayload = { + events: SuncoEvent[] +} + +export type SuncoEvent = ConversationMessageEvent | SwitchboardReleaseControlEvent + +// Not typing the rest since we don't use them +export type SuncoOtherMessageContent = { + type: 'carousel' | 'form' | 'formResponse' | 'list' | 'location' | 'template' +} + +export type SuncoTextMessageContent = { + type: 'text' + text?: string + htmlText?: string + blockChatInput?: boolean + actions?: unknown[] + payload?: string +} + +export type SuncoImageMessageContent = { + type: 'image' + mediaUrl: string + mediaType?: string + mediaSize?: number + altText?: string + text?: string + blockChatInput?: boolean + htmlText?: string + actions?: unknown[] + attachmentId?: string +} + +export type SuncoFileMessageContent = { + type: 'file' + mediaUrl: string + mediaSize?: number + mediaType?: string + altText?: string + text?: string + blockChatInput?: boolean + htmlText?: string + attachmentId?: string +} + +export type SuncoMessageContent = + | SuncoTextMessageContent + | SuncoImageMessageContent + | SuncoFileMessageContent + | SuncoOtherMessageContent + +export type ConversationMessageEvent = { + id: string + createdAt: string + type: 'conversation:message' + payload: { + conversation?: { + id: string + type?: string + activeSwitchboardIntegration?: { + id: string + name: string + integrationId: string + integrationType: string + } + pendingSwitchboardIntegration?: { + id: string + name: string + integrationId: string + integrationType: string + } + } + message: { + id: string + received: string + author: { + type: string + displayName?: string + avatarUrl?: string + } + content: SuncoMessageContent + metadata?: Record + source?: { + originalMessageTimestamp?: string + type?: string + } + } + } +} + +export type SwitchboardReleaseControlEvent = { + id: string + createdAt: string + type: 'switchboard:releaseControl' + payload: { + conversation?: { + id: string + } + reason?: string + } +} + +export function isSuncoWebhookPayload(data: unknown): data is SuncoWebhookPayload { + return z + .object({ + events: z.array(z.unknown()), + }) + .safeParse(data).success +} diff --git a/integrations/zendesk-messaging-hitl/src/types.ts b/integrations/zendesk-messaging-hitl/src/types.ts new file mode 100644 index 00000000000..a4ffb3c7b54 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/types.ts @@ -0,0 +1,29 @@ +import * as sdk from '@botpress/sdk' +import * as bp from '.botpress' + +export type Handler = bp.IntegrationProps['handler'] +export type HandlerProps = bp.HandlerProps +export type IntegrationCtx = bp.Context +export type Logger = bp.Logger + +export type Client = bp.Client +export type Conversation = bp.ClientResponses['listConversations']['conversations'][number] +export type Message = bp.ClientResponses['getMessage']['message'] +export type User = bp.ClientResponses['getUser']['user'] +export type Event = bp.ClientResponses['getEvent']['event'] + +export type EventDefinition = sdk.EventDefinition +export type ActionDefinition = sdk.ActionDefinition +export type ChannelDefinition = sdk.ChannelDefinition +export type MessageDefinition = sdk.MessageDefinition + +export type ActionProps = bp.AnyActionProps +export type MessageHandlerProps = bp.AnyMessageProps +export type AckFunction = bp.AnyAckFunction +export type CreateMessageInput = Parameters[0] +export type CreateMessageInputType = CreateMessageInput['type'] +export type CreateMessageInputPayload = CreateMessageInput['payload'] + +export type StartHitlInput = Parameters[0]['input'] +export type HitlSession = StartHitlInput['hitlSession'] +export type MessageHistory = StartHitlInput['messageHistory'] diff --git a/integrations/zendesk-messaging-hitl/src/util.ts b/integrations/zendesk-messaging-hitl/src/util.ts new file mode 100644 index 00000000000..73e03fe74f6 --- /dev/null +++ b/integrations/zendesk-messaging-hitl/src/util.ts @@ -0,0 +1,31 @@ +export type FileMetadata = { mimeType: string; fileSize?: number; fileName?: string } + +export async function getMediaMetadata(url: string): Promise { + const response = await fetch(url, { method: 'HEAD' }) + + if (!response.ok) { + throw new Error(`Failed to fetch metadata for URL: ${url}`) + } + + const mimeType = response.headers.get('content-type') ?? 'application/octet-stream' + const contentLength = response.headers.get('content-length') + const contentDisposition = response.headers.get('content-disposition') + + const fileSize = contentLength ? Number(contentLength) : undefined + + // Try to extract filename from content-disposition + let fileName: string | undefined + if (contentDisposition) { + const match = contentDisposition.match(/filename\*?=(?:UTF-8'')?"?([^"]+)"?/i) + const rawFileName = match?.[1] + if (rawFileName) { + fileName = decodeURIComponent(rawFileName) + } + } + + return { + mimeType, + fileSize, + fileName, + } +} diff --git a/integrations/zendesk-messaging-hitl/tsconfig.json b/integrations/zendesk-messaging-hitl/tsconfig.json new file mode 100644 index 00000000000..0c1062fd8ce --- /dev/null +++ b/integrations/zendesk-messaging-hitl/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist" + }, + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ead4f4f1e3..07317d986e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2133,6 +2133,31 @@ importers: specifier: ^4.14.191 version: 4.14.195 + integrations/zendesk-messaging-hitl: + dependencies: + '@botpress/cli': + specifier: workspace:* + version: link:../../packages/cli + '@botpress/client': + specifier: workspace:* + version: link:../../packages/client + '@botpress/common': + specifier: workspace:* + version: link:../../packages/common + '@botpress/sdk': + specifier: workspace:* + version: link:../../packages/sdk + '@botpress/sdk-addons': + specifier: workspace:* + version: link:../../packages/sdk-addons + sunshine-conversations-client: + specifier: ^9.12.0 + version: 9.14.0(@babel/core@7.28.0) + devDependencies: + '@sentry/cli': + specifier: ^2.39.1 + version: 2.39.1 + integrations/zoho: dependencies: '@botpress/client': @@ -3567,7 +3592,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==, tarball: https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz} engines: {node: '>=6.9.0'} '@babel/core@7.26.9': @@ -3575,7 +3600,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==, tarball: https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz} engines: {node: '>=6.9.0'} '@babel/generator@7.26.9': @@ -3587,7 +3612,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.27.1': @@ -3599,7 +3624,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==, tarball: https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz} engines: {node: '>=6.9.0'} '@babel/helper-create-class-features-plugin@7.27.1': @@ -3609,7 +3634,7 @@ packages: '@babel/core': ^7.0.0 '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, tarball: https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz} engines: {node: '>=6.9.0'} '@babel/helper-member-expression-to-functions@7.27.1': @@ -3637,7 +3662,7 @@ packages: '@babel/core': ^7.0.0 '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==, tarball: https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -3681,7 +3706,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, tarball: https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz} engines: {node: '>=6.9.0'} '@babel/helpers@7.26.9': @@ -3689,7 +3714,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helpers@7.28.3': - resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==, tarball: https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz} engines: {node: '>=6.9.0'} '@babel/parser@7.26.9': @@ -3703,7 +3728,7 @@ packages: hasBin: true '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz} engines: {node: '>=6.0.0'} hasBin: true @@ -3829,7 +3854,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/traverse@7.28.3': - resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz} engines: {node: '>=6.9.0'} '@babel/types@7.26.9': @@ -3841,7 +3866,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz} engines: {node: '>=6.9.0'} '@bcherny/json-schema-ref-parser@10.0.5-fork': @@ -3948,577 +3973,577 @@ packages: engines: {node: '>=20.11.0'} '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz} engines: {node: '>=18'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -4736,7 +4761,7 @@ packages: resolution: {integrity: sha512-JctBiLmRpxEp83gJWhDcBuFqm5X7T683OLmncN9g0chAHkC8+y5cJmgknaAk5Rb/ANDR3pXMMnGdnGXDdysfBQ==} '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, tarball: https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz} '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} @@ -4757,7 +4782,7 @@ packages: resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, tarball: https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -4766,7 +4791,7 @@ packages: resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz} '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -4852,7 +4877,7 @@ packages: engines: {node: '>=18'} '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': - resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} + resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==, tarball: https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -4990,42 +5015,42 @@ packages: resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} '@oxlint/darwin-arm64@1.14.0': - resolution: {integrity: sha512-rcTw0QWeOc6IeVp+Up7WtcwdS9l4j7TOq4tihF0Ud/fl+VUVdvDCPuZ9QTnLXJhwMXiyQRWdxRyI6XBwf80ncQ==} + resolution: {integrity: sha512-rcTw0QWeOc6IeVp+Up7WtcwdS9l4j7TOq4tihF0Ud/fl+VUVdvDCPuZ9QTnLXJhwMXiyQRWdxRyI6XBwf80ncQ==, tarball: https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.14.0.tgz} cpu: [arm64] os: [darwin] '@oxlint/darwin-x64@1.14.0': - resolution: {integrity: sha512-TWFSEmyl2/DN4HoXNwQl0y/y3EXFJDctfv5MiDtVOV1GJKX80cGSIxMxXb08Q3CCWqteqEijmfSMo5TG8X1H/A==} + resolution: {integrity: sha512-TWFSEmyl2/DN4HoXNwQl0y/y3EXFJDctfv5MiDtVOV1GJKX80cGSIxMxXb08Q3CCWqteqEijmfSMo5TG8X1H/A==, tarball: https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.14.0.tgz} cpu: [x64] os: [darwin] '@oxlint/linux-arm64-gnu@1.14.0': - resolution: {integrity: sha512-N1FqdKfwhVWPpMElv8qlGqdEefTbDYaRVhdGWOjs/2f7FESa5vX0cvA7ToqzkoXyXZI5DqByWiPML33njK30Kg==} + resolution: {integrity: sha512-N1FqdKfwhVWPpMElv8qlGqdEefTbDYaRVhdGWOjs/2f7FESa5vX0cvA7ToqzkoXyXZI5DqByWiPML33njK30Kg==, tarball: https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.14.0.tgz} cpu: [arm64] os: [linux] '@oxlint/linux-arm64-musl@1.14.0': - resolution: {integrity: sha512-v/BPuiateLBb7Gz1STb69EWjkgKdlPQ1NM56z+QQur21ly2hiMkBX2n0zEhqfu9PQVRUizu6AlsYuzcPY/zsIQ==} + resolution: {integrity: sha512-v/BPuiateLBb7Gz1STb69EWjkgKdlPQ1NM56z+QQur21ly2hiMkBX2n0zEhqfu9PQVRUizu6AlsYuzcPY/zsIQ==, tarball: https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.14.0.tgz} cpu: [arm64] os: [linux] '@oxlint/linux-x64-gnu@1.14.0': - resolution: {integrity: sha512-gUTp8KIrSYt97dn+tRRC3LKnH4xlHKCwrPwiDcGmLbCxojuN9/H5mnIhPKEfwNuZNdoKGS/ABuq3neVyvRCRtQ==} + resolution: {integrity: sha512-gUTp8KIrSYt97dn+tRRC3LKnH4xlHKCwrPwiDcGmLbCxojuN9/H5mnIhPKEfwNuZNdoKGS/ABuq3neVyvRCRtQ==, tarball: https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.14.0.tgz} cpu: [x64] os: [linux] '@oxlint/linux-x64-musl@1.14.0': - resolution: {integrity: sha512-DpN6cW2HPjYXeENG0JBbmubO8LtfKt6qJqEMBw9gUevbyBaX+k+Jn7sYgh6S23wGOkzmTNphBsf/7ulj4nIVYA==} + resolution: {integrity: sha512-DpN6cW2HPjYXeENG0JBbmubO8LtfKt6qJqEMBw9gUevbyBaX+k+Jn7sYgh6S23wGOkzmTNphBsf/7ulj4nIVYA==, tarball: https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.14.0.tgz} cpu: [x64] os: [linux] '@oxlint/win32-arm64@1.14.0': - resolution: {integrity: sha512-oXxJksnUTUMgJ0NvjKS1mrCXAy1ttPgIVacRSlxQ+1XHy+aJDMM7I8fsCtoKoEcTIpPaD98eqUqlLYs0H2MGjA==} + resolution: {integrity: sha512-oXxJksnUTUMgJ0NvjKS1mrCXAy1ttPgIVacRSlxQ+1XHy+aJDMM7I8fsCtoKoEcTIpPaD98eqUqlLYs0H2MGjA==, tarball: https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.14.0.tgz} cpu: [arm64] os: [win32] '@oxlint/win32-x64@1.14.0': - resolution: {integrity: sha512-iRYy2rhTQKFztyx0jtNMRBnFpzsRwFdjWQ7sKKzJpmbijA3Tw3DCqlGT7QRgoVRF0+X/ccNGvvsrgMohPVfLeQ==} + resolution: {integrity: sha512-iRYy2rhTQKFztyx0jtNMRBnFpzsRwFdjWQ7sKKzJpmbijA3Tw3DCqlGT7QRgoVRF0+X/ccNGvvsrgMohPVfLeQ==, tarball: https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.14.0.tgz} cpu: [x64] os: [win32] @@ -5034,7 +5059,7 @@ packages: engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} engines: {node: '>=14'} '@pkgr/core@0.2.9': @@ -5226,92 +5251,92 @@ packages: '@redis/client': ^1.0.0 '@rollup/rollup-android-arm-eabi@4.24.2': - resolution: {integrity: sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==} + resolution: {integrity: sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.24.2': - resolution: {integrity: sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==} + resolution: {integrity: sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.24.2': - resolution: {integrity: sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==} + resolution: {integrity: sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.24.2': - resolution: {integrity: sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==} + resolution: {integrity: sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.24.2': - resolution: {integrity: sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==} + resolution: {integrity: sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.24.2': - resolution: {integrity: sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==} + resolution: {integrity: sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.24.2': - resolution: {integrity: sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==} + resolution: {integrity: sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.24.2': - resolution: {integrity: sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==} + resolution: {integrity: sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.24.2': - resolution: {integrity: sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==} + resolution: {integrity: sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz} cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.24.2': - resolution: {integrity: sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==} + resolution: {integrity: sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz} cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.24.2': - resolution: {integrity: sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==} + resolution: {integrity: sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz} cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.24.2': - resolution: {integrity: sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==} + resolution: {integrity: sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz} cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.24.2': - resolution: {integrity: sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==} + resolution: {integrity: sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz} cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.24.2': - resolution: {integrity: sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==} + resolution: {integrity: sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz} cpu: [x64] os: [linux] '@rollup/rollup-linux-x64-musl@4.24.2': - resolution: {integrity: sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==} + resolution: {integrity: sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz} cpu: [x64] os: [linux] '@rollup/rollup-win32-arm64-msvc@4.24.2': - resolution: {integrity: sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==} + resolution: {integrity: sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.24.2': - resolution: {integrity: sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==} + resolution: {integrity: sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.24.2': - resolution: {integrity: sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==} + resolution: {integrity: sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz} cpu: [x64] os: [win32] @@ -5364,42 +5389,42 @@ packages: engines: {node: '>=8'} '@sentry/cli-darwin@2.39.1': - resolution: {integrity: sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==} + resolution: {integrity: sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==, tarball: https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz} engines: {node: '>=10'} os: [darwin] '@sentry/cli-linux-arm64@2.39.1': - resolution: {integrity: sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==} + resolution: {integrity: sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==, tarball: https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz} engines: {node: '>=10'} cpu: [arm64] os: [linux, freebsd] '@sentry/cli-linux-arm@2.39.1': - resolution: {integrity: sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==} + resolution: {integrity: sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==, tarball: https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz} engines: {node: '>=10'} cpu: [arm] os: [linux, freebsd] '@sentry/cli-linux-i686@2.39.1': - resolution: {integrity: sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==} + resolution: {integrity: sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==, tarball: https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz} engines: {node: '>=10'} cpu: [x86, ia32] os: [linux, freebsd] '@sentry/cli-linux-x64@2.39.1': - resolution: {integrity: sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==} + resolution: {integrity: sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==, tarball: https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz} engines: {node: '>=10'} cpu: [x64] os: [linux, freebsd] '@sentry/cli-win32-i686@2.39.1': - resolution: {integrity: sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==} + resolution: {integrity: sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==, tarball: https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz} engines: {node: '>=10'} cpu: [x86, ia32] os: [win32] '@sentry/cli-win32-x64@2.39.1': - resolution: {integrity: sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==} + resolution: {integrity: sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==, tarball: https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz} engines: {node: '>=10'} cpu: [x64] os: [win32] @@ -6699,7 +6724,7 @@ packages: hasBin: true browserslist@4.25.4: - resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==} + resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -6809,7 +6834,7 @@ packages: resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} caniuse-lite@1.0.30001739: - resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==} + resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz} capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -6864,7 +6889,7 @@ packages: engines: {node: '>= 8.10.0'} chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==, tarball: https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz} engines: {node: '>= 8.10.0'} chokidar@4.0.3: @@ -7317,7 +7342,7 @@ packages: resolution: {integrity: sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==} electron-to-chromium@1.5.214: - resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==} + resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -7885,7 +7910,7 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -8794,7 +8819,7 @@ packages: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} jsonpath-plus@6.0.1: - resolution: {integrity: sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==} + resolution: {integrity: sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==, tarball: https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz} engines: {node: '>=10.0.0'} jsonpath-plus@7.1.0: @@ -9720,7 +9745,7 @@ packages: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} pg-cloudflare@1.2.7: - resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==, tarball: https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz} pg-connection-string@2.7.0: resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} @@ -10274,32 +10299,32 @@ packages: engines: {node: '>=8'} sherif-darwin-arm64@1.0.1: - resolution: {integrity: sha512-eDYzUO6ksjYZF6swtoJ5F4AVa4og+sqrs0H7gGw57FoiMHEe5+4tD4M2ojTbVAaeeFtRS0gMbVZFGkhSAshQMw==} + resolution: {integrity: sha512-eDYzUO6ksjYZF6swtoJ5F4AVa4og+sqrs0H7gGw57FoiMHEe5+4tD4M2ojTbVAaeeFtRS0gMbVZFGkhSAshQMw==, tarball: https://registry.npmjs.org/sherif-darwin-arm64/-/sherif-darwin-arm64-1.0.1.tgz} cpu: [arm64] os: [darwin] sherif-darwin-x64@1.0.1: - resolution: {integrity: sha512-lnNZTck5F2eZoB1nZFwixfFQe/DXt4HuOMNCTG+Iyzq8w7kdSXUr/IIPYk2p88L+d4AtPWApmKE6mpmQaDzOVg==} + resolution: {integrity: sha512-lnNZTck5F2eZoB1nZFwixfFQe/DXt4HuOMNCTG+Iyzq8w7kdSXUr/IIPYk2p88L+d4AtPWApmKE6mpmQaDzOVg==, tarball: https://registry.npmjs.org/sherif-darwin-x64/-/sherif-darwin-x64-1.0.1.tgz} cpu: [x64] os: [darwin] sherif-linux-arm64@1.0.1: - resolution: {integrity: sha512-/fj3rJoSb26FA5wRlZpauLFaFeWCe9usIQAE+e9IEaC2751IU9I0d+nE7pmGmuOwTCR1/kPOlrDGfV4ucpP/ng==} + resolution: {integrity: sha512-/fj3rJoSb26FA5wRlZpauLFaFeWCe9usIQAE+e9IEaC2751IU9I0d+nE7pmGmuOwTCR1/kPOlrDGfV4ucpP/ng==, tarball: https://registry.npmjs.org/sherif-linux-arm64/-/sherif-linux-arm64-1.0.1.tgz} cpu: [arm64] os: [linux] sherif-linux-x64@1.0.1: - resolution: {integrity: sha512-I07H4VAPM0+N8rpIgxV47vBSKViJ3F/EiaUi4DnlhySivGg/6oOacHFOfzKeNPMaMK+3usMEmU+UVm6vEvtPYA==} + resolution: {integrity: sha512-I07H4VAPM0+N8rpIgxV47vBSKViJ3F/EiaUi4DnlhySivGg/6oOacHFOfzKeNPMaMK+3usMEmU+UVm6vEvtPYA==, tarball: https://registry.npmjs.org/sherif-linux-x64/-/sherif-linux-x64-1.0.1.tgz} cpu: [x64] os: [linux] sherif-windows-arm64@1.0.1: - resolution: {integrity: sha512-UfIYXDya60VmAcYuuKe6bwnBWiJHA6lof97rG2+EN8f3LiGyEx95/3qUdUoOn7qB5pl3xaeazMGcpc1O4FpExg==} + resolution: {integrity: sha512-UfIYXDya60VmAcYuuKe6bwnBWiJHA6lof97rG2+EN8f3LiGyEx95/3qUdUoOn7qB5pl3xaeazMGcpc1O4FpExg==, tarball: https://registry.npmjs.org/sherif-windows-arm64/-/sherif-windows-arm64-1.0.1.tgz} cpu: [arm64] os: [win32] sherif-windows-x64@1.0.1: - resolution: {integrity: sha512-ep+acRpTeIhU3BB7GL1NdedD9ubhcIO1lwOJ9uf3rOrmKIlbKd55LgmVRfl/Spy96qcCGx6izdyypMhDOoWa/Q==} + resolution: {integrity: sha512-ep+acRpTeIhU3BB7GL1NdedD9ubhcIO1lwOJ9uf3rOrmKIlbKd55LgmVRfl/Spy96qcCGx6izdyypMhDOoWa/Q==, tarball: https://registry.npmjs.org/sherif-windows-x64/-/sherif-windows-x64-1.0.1.tgz} cpu: [x64] os: [win32] @@ -10806,32 +10831,32 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} turbo-darwin-64@2.3.3: - resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==} + resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.3.3.tgz} cpu: [x64] os: [darwin] turbo-darwin-arm64@2.3.3: - resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==} + resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.3.tgz} cpu: [arm64] os: [darwin] turbo-linux-64@2.3.3: - resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==} + resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.3.3.tgz} cpu: [x64] os: [linux] turbo-linux-arm64@2.3.3: - resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==} + resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.3.3.tgz} cpu: [arm64] os: [linux] turbo-windows-64@2.3.3: - resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==} + resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.3.3.tgz} cpu: [x64] os: [win32] turbo-windows-arm64@2.3.3: - resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==} + resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.3.3.tgz} cpu: [arm64] os: [win32] @@ -10925,7 +10950,7 @@ packages: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==, tarball: https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz} engines: {node: '>=0.8.0'} hasBin: true @@ -11003,7 +11028,7 @@ packages: browserslist: '>= 4.21.0' update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz} hasBin: true peerDependencies: browserslist: '>= 4.21.0'