diff --git a/.playwright-mcp/page-2026-04-01T07-13-27-345Z.yml b/.playwright-mcp/page-2026-04-01T07-13-27-345Z.yml deleted file mode 100644 index 82ce244..0000000 --- a/.playwright-mcp/page-2026-04-01T07-13-27-345Z.yml +++ /dev/null @@ -1,316 +0,0 @@ -- generic [active] [ref=e1]: - - generic [ref=e2]: - - link "Skip to main content" [ref=e3] [cursor=pointer]: - - /url: "#content-area" - - generic [ref=e5]: - - generic [ref=e9]: - - generic [ref=e12]: - - generic [ref=e13]: - - link "OpenClaw home page light logo" [ref=e14] [cursor=pointer]: - - /url: / - - generic [ref=e15]: OpenClaw home page - - img "light logo" [ref=e16] - - button "US English" [ref=e18] [cursor=pointer]: - - img "US" [ref=e20] - - generic [ref=e22]: English - - img [ref=e23] - - button "Open search" [ref=e26] [cursor=pointer]: - - generic [ref=e27]: - - img [ref=e28] - - generic [ref=e31]: Search... - - generic [ref=e32]: ⌘K - - generic [ref=e33]: - - navigation [ref=e34]: - - list [ref=e35]: - - listitem [ref=e36]: - - link "GitHub" [ref=e37] [cursor=pointer]: - - /url: https://github.com/openclaw/openclaw - - img [ref=e38] - - text: GitHub - - listitem [ref=e39]: - - link "Releases" [ref=e40] [cursor=pointer]: - - /url: https://github.com/openclaw/openclaw/releases - - img [ref=e41] - - text: Releases - - listitem [ref=e42]: - - link "Discord" [ref=e43] [cursor=pointer]: - - /url: https://discord.com/invite/clawd - - img [ref=e44] - - text: Discord - - button "Toggle dark mode" [ref=e46] [cursor=pointer]: - - img [ref=e47] - - generic [ref=e55]: - - link "Get started" [ref=e56] [cursor=pointer]: - - /url: / - - text: Get started - - link "Install" [ref=e58] [cursor=pointer]: - - /url: /install - - text: Install - - link "Channels" [ref=e60] [cursor=pointer]: - - /url: /channels - - text: Channels - - link "Agents" [ref=e62] [cursor=pointer]: - - /url: /concepts/architecture - - text: Agents - - link "Tools & Plugins" [ref=e64] [cursor=pointer]: - - /url: /tools - - text: Tools & Plugins - - link "Models" [ref=e66] [cursor=pointer]: - - /url: /providers - - text: Models - - link "Platforms" [ref=e68] [cursor=pointer]: - - /url: /platforms - - text: Platforms - - link "Gateway & Ops" [ref=e70] [cursor=pointer]: - - /url: /gateway - - text: Gateway & Ops - - link "Reference" [ref=e72] [cursor=pointer]: - - /url: /cli - - text: Reference - - link "Help" [ref=e74] [cursor=pointer]: - - /url: /help - - text: Help - - generic [ref=e76]: - - generic [ref=e80]: - - generic [ref=e81]: - - heading "Overview" [level=5] [ref=e83] - - list [ref=e84]: - - listitem [ref=e85]: - - link "OpenClaw" [ref=e86] [cursor=pointer]: - - /url: / - - generic [ref=e89]: OpenClaw - - listitem [ref=e90]: - - link "Showcase" [ref=e91] [cursor=pointer]: - - /url: /start/showcase - - generic [ref=e94]: Showcase - - listitem [ref=e95]: - - link "Features" [ref=e96] [cursor=pointer]: - - /url: /concepts/features - - generic [ref=e99]: Features - - generic [ref=e100]: - - heading "First steps" [level=5] [ref=e102] - - list [ref=e103]: - - listitem [ref=e104]: - - link "Getting Started" [ref=e105] [cursor=pointer]: - - /url: /start/getting-started - - generic [ref=e108]: Getting Started - - listitem [ref=e109]: - - link "Onboarding Overview" [ref=e110] [cursor=pointer]: - - /url: /start/onboarding-overview - - generic [ref=e113]: Onboarding Overview - - listitem [ref=e114]: - - 'link "Onboarding: CLI" [ref=e115] [cursor=pointer]': - - /url: /start/wizard - - generic [ref=e118]: "Onboarding: CLI" - - listitem [ref=e119]: - - 'link "Onboarding: macOS App" [ref=e120] [cursor=pointer]': - - /url: /start/onboarding - - generic [ref=e123]: "Onboarding: macOS App" - - generic [ref=e124]: - - heading "Guides" [level=5] [ref=e126] - - list [ref=e127]: - - listitem [ref=e128]: - - link "Personal Assistant Setup" [ref=e129] [cursor=pointer]: - - /url: /start/openclaw - - generic [ref=e132]: Personal Assistant Setup - - listitem [ref=e133]: - - link "CLI reference" [ref=e134] [cursor=pointer]: - - /url: /start/wizard-cli-reference - - generic [ref=e137]: CLI reference - - listitem [ref=e138]: - - link "CLI automation" [ref=e139] [cursor=pointer]: - - /url: /start/wizard-cli-automation - - generic [ref=e142]: CLI automation - - generic [ref=e145]: - - banner [ref=e146]: - - generic [ref=e147]: - - generic [ref=e148]: First steps - - heading "Getting Started" [level=1] [ref=e150] - - generic [ref=e151]: - - generic [ref=e152]: Install OpenClaw, run onboarding, and chat with your AI assistant — all in about 5 minutes. By the end you will have a running Gateway, configured auth, and a working chat session. - - heading "Navigate to header What you need" [level=2] [ref=e153]: - - link "Navigate to header" [ref=e154] [cursor=pointer]: - - /url: "#what-you-need" - - img [ref=e156] - - generic [ref=e158] [cursor=pointer]: What you need - - list [ref=e159]: - - listitem [ref=e160]: - - strong [ref=e161]: Node.js - - text: — Node 24 recommended (Node 22.14+ also supported) - - listitem [ref=e162]: - - strong [ref=e163]: An API key - - text: from a model provider (Anthropic, OpenAI, Google, etc.) — onboarding will prompt you - - generic [ref=e164]: - - img "Tip" [ref=e166] - - generic [ref=e169]: - - text: Check your Node version with - - code [ref=e170]: node --version - - text: . - - strong [ref=e171]: "Windows users:" - - text: both native Windows and WSL2 are supported. WSL2 is more stable and recommended for the full experience. See - - link "Windows" [ref=e172] [cursor=pointer]: - - /url: /platforms/windows - - text: . Need to install Node? See - - link "Node setup" [ref=e173] [cursor=pointer]: - - /url: /install/node - - text: . - - heading "Navigate to header Quick setup" [level=2] [ref=e174]: - - link "Navigate to header" [ref=e175] [cursor=pointer]: - - /url: "#quick-setup" - - img [ref=e177] - - generic [ref=e179] [cursor=pointer]: Quick setup - - list [ref=e180]: - - listitem [ref=e181]: - - generic [ref=e184]: - - generic [ref=e185]: "1" - - link "Navigate to header" [ref=e187] [cursor=pointer]: - - /url: "#" - - img [ref=e189] - - generic [ref=e191]: - - paragraph [ref=e192]: Install OpenClaw - - generic [ref=e193]: - - generic [ref=e194]: - - tablist [ref=e195]: - - tab "macOS / Linux" [selected] [ref=e196] [cursor=pointer]: - - generic [ref=e197]: macOS / Linux - - tab "Windows (PowerShell)" [ref=e198] [cursor=pointer]: - - generic [ref=e199]: Windows (PowerShell) - - 'tabpanel "macOS / Linux Windows (PowerShell) Copy the contents from the code block curl -fsSL https://openclaw.ai/install.sh | bash Install Script Process Expand image: Install Script Process" [ref=e201]': - - generic [ref=e202]: - - button "Copy the contents from the code block" [ref=e205] [cursor=pointer]: - - img [ref=e206] - - code [ref=e212]: - - generic [ref=e213]: curl -fsSL https://openclaw.ai/install.sh | bash - - generic [ref=e214]: - - img "Install Script Process" [ref=e217] - - generic: - - 'button "Expand image: Install Script Process"' - - generic [ref=e218]: - - img "Note" [ref=e220] - - generic [ref=e223]: - - text: "Other install methods (Docker, Nix, npm):" - - link "Install" [ref=e224] [cursor=pointer]: - - /url: /install - - text: . - - listitem [ref=e225]: - - generic [ref=e228]: - - generic [ref=e229]: "2" - - link "Navigate to header" [ref=e231] [cursor=pointer]: - - /url: "#" - - img [ref=e233] - - generic [ref=e235]: - - paragraph [ref=e236]: Run onboarding - - generic [ref=e237]: - - generic [ref=e238]: - - button "Copy the contents from the code block" [ref=e241] [cursor=pointer]: - - img [ref=e242] - - code [ref=e248]: - - generic [ref=e249]: openclaw onboard --install-daemon - - generic [ref=e250]: The wizard walks you through choosing a model provider, setting an API key, and configuring the Gateway. It takes about 2 minutes. - - generic [ref=e251]: - - text: See - - link "Onboarding (CLI)" [ref=e252] [cursor=pointer]: - - /url: /start/wizard - - text: for the full reference. - - listitem [ref=e253]: - - generic [ref=e256]: - - generic [ref=e257]: "3" - - link "Navigate to header" [ref=e259] [cursor=pointer]: - - /url: "#" - - img [ref=e261] - - generic [ref=e263]: - - paragraph [ref=e264]: Verify the Gateway is running - - generic [ref=e265]: - - generic [ref=e266]: - - button "Copy the contents from the code block" [ref=e269] [cursor=pointer]: - - img [ref=e270] - - code [ref=e276]: - - generic [ref=e277]: openclaw gateway status - - generic [ref=e278]: You should see the Gateway listening on port 18789. - - listitem [ref=e279]: - - generic [ref=e282]: - - generic [ref=e283]: "4" - - link "Navigate to header" [ref=e285] [cursor=pointer]: - - /url: "#" - - img [ref=e287] - - generic [ref=e289]: - - paragraph [ref=e290]: Open the dashboard - - generic [ref=e291]: - - generic [ref=e292]: - - button "Copy the contents from the code block" [ref=e295] [cursor=pointer]: - - img [ref=e296] - - code [ref=e302]: - - generic [ref=e303]: openclaw dashboard - - generic [ref=e304]: This opens the Control UI in your browser. If it loads, everything is working. - - listitem [ref=e305]: - - generic [ref=e308]: - - generic [ref=e309]: "5" - - link "Navigate to header" [ref=e311] [cursor=pointer]: - - /url: "#" - - img [ref=e313] - - generic [ref=e315]: - - paragraph [ref=e316]: Send your first message - - generic [ref=e317]: - - generic [ref=e318]: Type a message in the Control UI chat and you should get an AI reply. - - generic [ref=e319]: - - text: Want to chat from your phone instead? The fastest channel to set up is - - link "Telegram" [ref=e320] [cursor=pointer]: - - /url: /channels/telegram - - text: (just a bot token). See - - link "Channels" [ref=e321] [cursor=pointer]: - - /url: /channels - - text: for all options. - - heading "Navigate to header What to do next" [level=2] [ref=e322]: - - link "Navigate to header" [ref=e323] [cursor=pointer]: - - /url: "#what-to-do-next" - - img [ref=e325] - - generic [ref=e327] [cursor=pointer]: What to do next - - generic [ref=e328]: - - link [ref=e329] [cursor=pointer]: - - link [ref=e331]: - - /url: /channels - - img [ref=e333] - - generic [ref=e334]: - - heading [level=2] [ref=e335]: Connect a channel - - generic [ref=e337]: WhatsApp, Telegram, Discord, iMessage, and more. - - link [ref=e338] [cursor=pointer]: - - link [ref=e340]: - - /url: /channels/pairing - - img [ref=e342] - - generic [ref=e343]: - - heading [level=2] [ref=e344]: Pairing and safety - - generic [ref=e346]: Control who can message your agent. - - link [ref=e347] [cursor=pointer]: - - link [ref=e349]: - - /url: /gateway/configuration - - img [ref=e351] - - generic [ref=e352]: - - heading [level=2] [ref=e353]: Configure the Gateway - - generic [ref=e355]: Models, tools, sandbox, and advanced settings. - - link [ref=e356] [cursor=pointer]: - - link [ref=e358]: - - /url: /tools - - img [ref=e360] - - generic [ref=e361]: - - heading [level=2] [ref=e362]: Browse tools - - generic [ref=e364]: Browser, exec, web search, skills, and plugins. - - group [ref=e365]: - - 'generic "Advanced: environment variables" [ref=e366] [cursor=pointer]': - - img [ref=e368] - - paragraph [ref=e370]: "Advanced: environment variables" - - generic [ref=e371]: - - link "Features" [ref=e372] [cursor=pointer]: - - /url: /concepts/features - - img [ref=e373] - - generic [ref=e375]: Features - - link "Onboarding Overview" [ref=e376] [cursor=pointer]: - - /url: /start/onboarding-overview - - generic [ref=e377]: Onboarding Overview - - img [ref=e378] - - contentinfo [ref=e380]: - - link "Powered by This documentation is built and hosted on Mintlify, a developer documentation platform" [ref=e383] [cursor=pointer]: - - /url: https://www.mintlify.com?utm_campaign=poweredBy&utm_medium=referral&utm_source=clawdhub - - generic [ref=e384]: Powered by - - img [ref=e385] - - generic [ref=e394]: This documentation is built and hosted on Mintlify, a developer documentation platform - - alert [ref=e395] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c2b0d8..87c8235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## Unreleased + +### Changed + +- Kept the runtime path on the external `simplex-chat` WebSocket API and removed the direct Node runtime dependency experiment from this branch. +- Restored the package posture to MIT-compatible runtime boundaries by avoiding direct `simplex-chat` package embedding. +- Reworked the runtime adapter into a capability-oriented WebSocket client while preserving the newer services/actions/events architecture. + ## [1.5.0] - 2026-04-26 ### Changed diff --git a/README.md b/README.md index e4250f4..dc96b7b 100644 --- a/README.md +++ b/README.md @@ -116,9 +116,10 @@ The key runtime boundary is explicit: OpenClaw does not own or supervise the `si - Direct and group messaging over SimpleX - Media send/receive support - Pairing approval, exec approval auth, and allowlist enforcement +- Context visibility controls and OpenClaw security audit findings for broad access or unsafe WebSocket config - Invite link, address link, and QR generation - Shared `message` actions including `upload-file`, reactions, polls, edits, deletes, and group actions -- Plugin tools and gateway methods for invite and group administration +- Plugin tools and gateway methods for invite/address management, runtime diagnostics, contact requests, group links, and link onboarding - Runtime status reporting, command handling, heartbeat readiness, and Control UI configuration - External WebSocket runtime integration with explicit operator-managed lifecycle @@ -126,6 +127,12 @@ The key runtime boundary is explicit: OpenClaw does not own or supervise the `si ## Install +Requirements: + +- OpenClaw `2026.5.4` or newer +- Node.js `22` or newer in the OpenClaw plugin host +- an external `simplex-chat` runtime reachable over WebSocket + ### 1. Install SimpleX CLI (`simplex-chat`) Official installer: @@ -206,7 +213,15 @@ This appends `openclaw-simplex` to the existing allowlist instead of replacing i } ``` -OpenClaw does not supervise `simplex-chat` for external plugins. If you want it to start automatically, run it as a host-managed user service such as `systemd --user` or `launchd`. +OpenClaw does not supervise `simplex-chat` for external plugins. If you want it to start automatically, run it as a host-managed user service such as `systemd --user`, `launchd`, or SysV init. + +The plugin CLI can generate that service for the current host: + +```bash +openclaw simplex runtime service install +``` + +It auto-detects user systemd on Linux, launchd on macOS, or SysV init as a Linux fallback, prints the target file and next commands, and asks for approval before writing the service file. It prints supervisor commands for you to run instead of executing them from the plugin, so install-time scanners do not see shell execution capability. Keep the split clear: @@ -215,6 +230,8 @@ Keep the split clear: For full persistent runtime examples and recommended startup-flag placement: https://openclaw-simplex.mintlify.app/guide/runtime-setup +Deployment templates also live in `examples/`, including a Docker sidecar compose file, a hardened systemd unit, and a Caddy TLS proxy example. + --- ## Invite and address management @@ -249,6 +266,17 @@ For automation and integrations, OpenClaw exposes gateway methods: - `simplex.invite.create` - `simplex.invite.list` - `simplex.invite.revoke` +- `simplex.runtime.status` +- `simplex.runtime.doctor` +- `simplex.requests.list` +- `simplex.requests.accept` +- `simplex.requests.reject` +- `simplex.groups.create` +- `simplex.groups.link.create` +- `simplex.groups.link.list` +- `simplex.groups.link.revoke` +- `simplex.connect.plan` +- `simplex.connect` --- @@ -274,13 +302,15 @@ This migrates: - `plugins.installs.simplex` → `plugins.installs.openclaw-simplex` - `plugins.allow` / `plugins.deny` entries from `simplex` → `openclaw-simplex` - `channels.simplex` → `channels.openclaw-simplex` +- legacy top-level runtime fields such as `wsUrl`, `url`, `host`, and `port` are moved under `connection` +- unsupported managed-mode fields such as `managed`, `cliPath`, `token`, and `dbFilePrefix` are removed from SimpleX config - OpenClaw pairing and allowlist state files under the OpenClaw state directory Current note: - The current plugin id is `openclaw-simplex` - The current channel id is `openclaw-simplex` -- Gateway method names remain `simplex.invite.*` +- Gateway method names keep the `simplex.*` prefix --- @@ -310,6 +340,11 @@ openclaw pairing list - `simplex.invite.create` - `simplex.invite.list` - `simplex.invite.revoke` +- `simplex.runtime.status` +- `simplex.runtime.doctor` +- `simplex.requests.*` +- `simplex.groups.*` +- `simplex.connect.*` **Plugin tools:** - `simplex_invite_create` @@ -328,6 +363,7 @@ openclaw pairing list | Plugin not visible | Check `plugins.allow` and run `openclaw plugins list` | | Channel not starting | Verify `channels.openclaw-simplex.connection` exists and points to a running SimpleX runtime | | `Configured No` | Add explicit `channels.openclaw-simplex.connection` config; plugin defaults alone are not enough for startup | +| `Running No` / `Connected No` | Check whether `simplex-chat` is reachable at `wsUrl`; the plugin reconnects after runtime restarts, but the endpoint still has to come back on the same address | | Inbound issues | Review `allowFrom`, `dmPolicy`, and group policy settings | | Media issues | Validate URLs and check size limits | diff --git a/biome.json b/biome.json index 1f3bf14..55750de 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json", "files": { "includes": ["**", "!**/dist"] }, diff --git a/docs/guide/architecture.mdx b/docs/guide/architecture.mdx index 7109c40..5d56cef 100644 --- a/docs/guide/architecture.mdx +++ b/docs/guide/architecture.mdx @@ -69,7 +69,8 @@ That separation keeps the channel implementation explicit, makes host-level supe ## Implementation surfaces -- src/channel/simplex-monitor.ts: inbound SimpleX event parsing and message normalization. -- src/channel/simplex-send.ts and src/channel/simplex-outbound.ts: outbound message/action translation. -- src/simplex/simplex-ws-client.ts and src/simplex/simplex-transport.ts: WebSocket transport and command execution. +- src/channel/events/simplex-monitor.ts: inbound SimpleX event monitoring, reconnect handling, parsing, and message normalization. +- src/channel/messaging/simplex-send.ts and src/channel/messaging/simplex-outbound.ts: outbound message delivery and reply translation. +- src/actions/actions.ts, src/actions/message-actions.ts, and src/actions/group-actions.ts: shared OpenClaw message actions such as upload-file, reactions, polls, edits, deletes, and group operations. +- src/simplex/runtime/ws-client.ts and src/simplex/runtime/transport.ts: WebSocket transport and command execution. - channels.openclaw-simplex and channels.openclaw-simplex.accounts: shared and per-account runtime config. diff --git a/docs/guide/getting-started.mdx b/docs/guide/getting-started.mdx index eb8426d..ab3f060 100644 --- a/docs/guide/getting-started.mdx +++ b/docs/guide/getting-started.mdx @@ -5,6 +5,12 @@ description: Install simplex-chat, configure the OpenClaw SimpleX plugin, genera This path gets you from zero to the first invited contact talking to your OpenClaw agent over SimpleX. +## Requirements + +- OpenClaw 2026.5.4 or newer +- Node.js 22 or newer in the OpenClaw plugin host +- a simplex-chat CLI runtime that can expose the WebSocket API + Install the official simplex-chat CLI: diff --git a/docs/guide/migration.mdx b/docs/guide/migration.mdx index af85a98..4e25ed1 100644 --- a/docs/guide/migration.mdx +++ b/docs/guide/migration.mdx @@ -12,10 +12,10 @@ Use this guide only if you previously configured or paired the plugin under the - pairing approval commands now use `openclaw-simplex` - Gateway method names did not change. Programmatic invite flows still use simplex.invite.create, simplex.invite.list, and simplex.invite.revoke. + Gateway method names keep the simplex.* prefix. Programmatic invite flows still use simplex.invite.create, simplex.invite.list, and simplex.invite.revoke; newer runtime, request, group, and link-onboarding methods use the same prefix. -The migration helper updates config keys and stored pairing/allowlist state so existing approvals can carry forward. +The migration helper updates config keys and stored pairing/allowlist state so existing approvals can carry forward. It also normalizes legacy runtime fields by moving supported WebSocket fields under connection and removing unsupported managed-mode fields. @@ -41,7 +41,7 @@ The migration helper updates config keys and stored pairing/allowlist state so e openclaw simplex migrate ``` - This rewrites the old simplex config keys and renames the related pairing/allowlist state files in the OpenClaw state directory. + This rewrites the old simplex config keys, normalizes legacy runtime fields for the external WebSocket runtime, and renames the related pairing/allowlist state files in the OpenClaw state directory. @@ -89,4 +89,6 @@ After migration, the channel should be configured under openclaw-simplex `plugins.installs.openclaw-simplex` - `plugins.allow` / `plugins.deny` - `channels.simplex` -> `channels.openclaw-simplex` +- legacy top-level runtime fields such as `wsUrl`, `url`, `host`, and `port` are moved under `connection` +- unsupported managed-mode fields such as `managed`, `cliPath`, `token`, and `dbFilePrefix` are removed from root and per-account SimpleX config - OpenClaw pairing and allowlist state files under the OpenClaw state directory diff --git a/docs/guide/runtime-setup.mdx b/docs/guide/runtime-setup.mdx index 1298a70..74a10f2 100644 --- a/docs/guide/runtime-setup.mdx +++ b/docs/guide/runtime-setup.mdx @@ -9,6 +9,33 @@ Use this page after the first setup is already working and you want to make the If you want the SimpleX runtime to start automatically on login or machine boot, let the host OS supervise it and keep OpenClaw focused on the channel connection itself. +The plugin CLI can generate and install a host service for the current machine: + +```bash +openclaw simplex runtime service install +``` + +The service helper auto-detects systemd --user on Linux, launchd on macOS, and SysV init as a Linux fallback. It prints the generated service file and next commands first, and it asks for interactive approval before writing files. It prints supervisor commands for the operator to run instead of executing them from the plugin. Use --provider systemd, --provider launchd, or --provider sysvinit to override detection. + +Common install options: + +```bash +openclaw simplex runtime service install \ + --port 5225 \ + --simplex-chat-path ~/.local/bin/simplex-chat \ + --device-name "OpenClaw SimpleX" \ + --files-folder ~/.simplex/files \ + --temp-folder ~/.simplex/tmp +``` + +Use --dry-run with install to print the plan without writing files. + +The repo also ships deployment templates under examples/: + +- examples/docker-compose.sidecar.yml: OpenClaw plus a private simplex-chat sidecar network. +- examples/systemd/simplex-chat.service: hardened user service template. +- examples/caddy/Caddyfile: TLS/auth proxy example for exceptional remote WebSocket access. + ## Decide which settings go where Use this rule: @@ -19,6 +46,7 @@ Use this rule: Plugin-side settings: - connection.wsUrl +- connection.allowUnsafeRemoteWs - connection.connectTimeoutMs - connection.autoAcceptFiles - dmPolicy, allowFrom, groupPolicy @@ -29,7 +57,9 @@ Runtime-side startup flags: - local storage/layout: --database, --files-folder, --temp-folder, --log-file - runtime process behavior: --device-name, --maintenance, --mute, --mark-read -### Create the service +### Manual service files + +The CLI helper above is the preferred path. These examples show what it writes and are useful when you want to manage the service definition yourself. @@ -151,3 +181,9 @@ These are reasonable patterns to consider when you run simplex-chat If you need to bring up the runtime without immediately serving chat traffic, run it with --maintenance and start chat manually inside the runtime later. This is a simplex-chat operational mode, not an OpenClaw channel config field, so it belongs in the service command only when you intentionally want that behavior. + +## Remote WebSocket endpoints + +Prefer loopback, a private sidecar network, or wss:// with access controls. Plaintext ws:// endpoints on non-loopback hosts are blocked by default because the WebSocket API controls the SimpleX runtime. + +Only set connection.allowUnsafeRemoteWs: true when that endpoint is protected by a private network, firewall, or authenticated TLS proxy. diff --git a/docs/guide/security-model.mdx b/docs/guide/security-model.mdx index f7bc778..06cf2d7 100644 --- a/docs/guide/security-model.mdx +++ b/docs/guide/security-model.mdx @@ -41,6 +41,7 @@ There are three boundaries to keep straight: - You decide where the runtime runs, which relay path it uses, and whether relays are public or self-hosted. - If you need a fully internal deployment, keep simplex-chat and your relays inside that network boundary. - External runtime mode is the supported deployment model for this plugin. OpenClaw does not spawn or supervise simplex-chat here. +- Plaintext remote WebSocket endpoints are blocked by default. Use loopback, private networking, or wss://; set connection.allowUnsafeRemoteWs only when the endpoint is protected by other controls. ## What this does not protect against @@ -52,5 +53,7 @@ There are three boundaries to keep straight: - Start with `dmPolicy: "pairing"` and `groupPolicy: "allowlist"`. - Keep `allowFrom`/`groupAllowFrom` narrow. +- Set `contextVisibility` deliberately if supplemental context exposure should be restricted for this channel. - Prefer one-time invite links for onboarding and revoke address links when they are no longer needed. -- Monitor status snapshots and logs for repeated authorization failures. +- Protect the SimpleX WebSocket endpoint, database, and file-transfer directories as sensitive local state. +- Monitor OpenClaw security audit findings, status snapshots, and logs for broad policies, unsafe WebSocket endpoints, or repeated authorization failures. diff --git a/docs/guide/troubleshooting.mdx b/docs/guide/troubleshooting.mdx index 22f5ca6..7949d4a 100644 --- a/docs/guide/troubleshooting.mdx +++ b/docs/guide/troubleshooting.mdx @@ -53,10 +53,21 @@ What to verify: - channels.openclaw-simplex.connection exists - wsUrl matches the port where simplex-chat is actually listening - if you use wsHost/wsPort instead, those values match the runtime endpoint +- if you point at a plaintext non-loopback ws:// endpoint, either move it behind loopback/private networking or explicitly set connection.allowUnsafeRemoteWs - channel status shows the latest connection error if the WebSocket handshake fails For persistent startup, run simplex-chat under a host-managed service. See [Runtime Setup](/guide/runtime-setup). +## Channel shows `Running No` or `Connected No` + +If the channel is configured but not connected, check the runtime boundary first: + +- Configured Yes means OpenClaw has enough channel config to attempt startup. +- Running No means OpenClaw does not currently have a running channel runtime for that account. +- Connected No means the plugin is not connected to the configured simplex-chat WebSocket endpoint. + +The plugin reconnects after an unexpected SimpleX WebSocket disconnect or runtime restart, but the restarted runtime must come back on the same configured endpoint. If the card stays disconnected, verify the service logs, port binding, and connection.wsUrl. + ## Inbound messages dropped If messages arrive in SimpleX but the agent does not answer, check policy first: @@ -93,6 +104,7 @@ If that fails: - confirm the account's connection.wsUrl points at a running runtime - check whether the channel account is enabled - inspect the latest channel status/runtime error in OpenClaw +- run openclaw simplex runtime status or openclaw simplex runtime doctor for the active account - if you are automating through gateway methods, verify the same account works through the plugin CLI first ## Media issues @@ -128,6 +140,24 @@ Write the channel endpoint config explicitly: openclaw channels add --channel openclaw-simplex --url ws://127.0.0.1:5225 ``` +Show runtime status: + +```bash +openclaw simplex runtime status +``` + +Run runtime diagnostics: + +```bash +openclaw simplex runtime doctor +``` + +List pending SimpleX contact requests: + +```bash +openclaw simplex requests list +``` + ## Where to inspect errors - In Control UI, open Control -> Channels -> SimpleX and inspect the channel status and connection state. diff --git a/docs/guide/why-simplex.mdx b/docs/guide/why-simplex.mdx index 98ffba8..8f2b9fb 100644 --- a/docs/guide/why-simplex.mdx +++ b/docs/guide/why-simplex.mdx @@ -37,8 +37,8 @@ A SimpleX channel backed by a local simplex-chat WebSocket runtime: - pairing with allowlist enforcement and same-chat exec approvals - shared `message` actions, including `upload-file`, reactions, polls, edits, deletes, and group actions - plugin tools for invite generation/list/revoke and group administration -- invite link and QR generation -- runtime status and heartbeat readiness support +- one-time invite link, SimpleX address, group-link, and QR generation +- runtime status, runtime doctor, contact-request review, group-link lifecycle, link onboarding, and heartbeat readiness support - Control UI configuration ## Why teams choose it @@ -56,7 +56,7 @@ The contribution is not just “another channel”. It adds a channel model that - no phone number requirement - no hosted bot account or bot API dependency -- access starts from an invitation link, not a public platform identity +- access starts from a one-time invite link or reusable SimpleX address, not a public platform identity Low risk to existing OpenClaw channels: the implementation stays isolated and is only active when openclaw-simplex is configured and enabled. diff --git a/docs/reference/config.mdx b/docs/reference/config.mdx index 7321d98..482f242 100644 --- a/docs/reference/config.mdx +++ b/docs/reference/config.mdx @@ -36,6 +36,7 @@ If accounts is not set, the root channels.openclaw-simplexaccounts is set, each account entry can override the shared root "name": "Ops", "enabled": true, "reactionLevel": "extensive", + "contextVisibility": "allowlist", "allowFrom": ["alice", "bob"], "connection": { "wsUrl": "ws://127.0.0.1:7777" @@ -89,6 +91,7 @@ If accounts is set, each account entry can override the shared root | `actions.polls` | Enable or disable poll-style message actions for this channel/account. | | `reactionLevel` | Agent reaction behavior: `off` \| `ack` \| `minimal` \| `extensive`. | | `dmPolicy` | Direct-message policy: `open` \| `allowlist` \| `pairing` \| `disabled`. Effective default: `pairing`. | +| `contextVisibility` | OpenClaw supplemental context visibility mode for the channel/account: `all` \| `allowlist` \| `allowlist_quote`. Use this when you want tighter control over what extra context the channel may expose to agent runs. | | `allowFrom` | DM allowlist entries. | | `dmHistoryLimit` | Optional per-DM history limit. | | `dms` | Per-DM config overrides using OpenClaw's shared DM config schema. | @@ -104,6 +107,7 @@ If accounts is set, each account entry can override the shared root | `connection.wsUrl` | WebSocket URL for the running `simplex-chat` WebSocket API. If set, this wins over `wsHost`/`wsPort`. | | `connection.wsHost` | Optional host fallback when `wsUrl` is not set. Default: `127.0.0.1`. | | `connection.wsPort` | Optional port fallback when `wsUrl` is not set. Default: `5225`. | +| `connection.allowUnsafeRemoteWs` | Allow plaintext remote WebSocket endpoints. Keep this false unless the endpoint is protected by a private network, firewall, or authenticated TLS proxy. | | `connection.autoAcceptFiles` | Whether incoming file transfers should be auto-accepted by the SimpleX runtime. | | `connection.connectTimeoutMs` | WebSocket connection timeout in milliseconds. | | `accounts.` | Per-account overrides for the same schema. | @@ -119,9 +123,10 @@ This plugin talks to an already running external simplex-chat proce ### Good fit for plugin config - connection.wsUrl, wsHost, wsPort +- connection.allowUnsafeRemoteWs - connection.connectTimeoutMs - connection.autoAcceptFiles -- OpenClaw policy and UX settings such as dmPolicy, allowFrom, groupPolicy, actions, and reactionLevel +- OpenClaw policy and UX settings such as dmPolicy, allowFrom, groupPolicy, contextVisibility, actions, and reactionLevel ### Intentionally not mirrored into plugin config diff --git a/docs/reference/example-commands.mdx b/docs/reference/example-commands.mdx index 7151f7b..c4ff7d0 100644 --- a/docs/reference/example-commands.mdx +++ b/docs/reference/example-commands.mdx @@ -1,6 +1,6 @@ --- title: Example Commands -description: Copy-paste CLI commands for common OpenClaw SimpleX tasks like generating invite links, approving contacts, listing paired users, and managing addresses. +description: Copy-paste CLI commands for common OpenClaw SimpleX tasks like runtime diagnostics, one-time invites, SimpleX addresses, contact requests, group links, pairing approval, and migration. --- Use this page as a command cookbook after the first setup is working. For the full first-run flow, start with [Getting Started](/guide/getting-started). @@ -54,7 +54,43 @@ openclaw plugins list openclaw plugins info openclaw-simplex ``` -If you already completed the first setup flow, you can skip this section and jump straight to invite, pairing, or migration commands below. +If you already completed the first setup flow, you can skip this section and jump straight to runtime, invite, contact-request, group-link, pairing, or migration commands below. + +## Runtime diagnostics + +Show the active SimpleX runtime state: + +```bash +openclaw simplex runtime status +``` + +Run the higher-level diagnostic summary: + +```bash +openclaw simplex runtime doctor +``` + +Use a named account when the channel has multiple accounts: + +```bash +openclaw simplex runtime doctor --account-id support +``` + +## Runtime service + +Install a user service for simplex-chat. The command prints the plan and asks before writing the service file: + +```bash +openclaw simplex runtime service install +``` + +Override auto-detection when needed: + +```bash +openclaw simplex runtime service install --provider systemd +openclaw simplex runtime service install --provider launchd +openclaw simplex runtime service install --provider sysvinit +``` ## Invite and address management @@ -88,6 +124,72 @@ Revoke the current address link: openclaw simplex address revoke ``` +## Contact requests + +List pending SimpleX contact requests observed by the runtime: + +```bash +openclaw simplex requests list +``` + +Accept a pending request: + +```bash +openclaw simplex requests accept --contact-request-id +``` + +Reject a pending request: + +```bash +openclaw simplex requests reject --contact-request-id +``` + +With multiple accounts: + +```bash +openclaw simplex requests list --account-id support +``` + +## Groups and group links + +Create a SimpleX group: + +```bash +openclaw simplex groups create --display-name "OpenClaw Ops" +``` + +Create a group invite link and print a terminal QR code: + +```bash +openclaw simplex groups link create --group-id --role member --qr +``` + +Show the current group invite link: + +```bash +openclaw simplex groups link list --group-id --qr +``` + +Revoke the current group invite link: + +```bash +openclaw simplex groups link revoke --group-id +``` + +## Link onboarding + +Preview what connecting to a SimpleX link would do: + +```bash +openclaw simplex connect plan --link "" +``` + +Connect the active SimpleX user to a contact, address, or group link: + +```bash +openclaw simplex connect run --link "" +``` + ## Pairing List pending pairing requests: @@ -129,4 +231,4 @@ Run these inside the simplex-chat console if you want to manage lin ## Gateway method names -For programmatic invite/address automation, the plugin exposes simplex.invite.create, simplex.invite.list, and simplex.invite.revoke. See [Gateway Methods](/reference/gateway-methods) for request and response shapes. +For programmatic automation, the plugin exposes gateway methods for one-time invite and SimpleX address management, runtime diagnostics, contact-request review, group-link lifecycle, and SimpleX link onboarding. See [Gateway Methods](/reference/gateway-methods) for request and response shapes. diff --git a/docs/reference/gateway-methods.mdx b/docs/reference/gateway-methods.mdx index 0e520c8..5180b5e 100644 --- a/docs/reference/gateway-methods.mdx +++ b/docs/reference/gateway-methods.mdx @@ -1,12 +1,12 @@ --- title: Gateway Methods -description: API reference for the OpenClaw SimpleX gateway RPC methods used to create invite links, generate address QR codes, and automate onboarding programmatically. +description: API reference for OpenClaw SimpleX gateway RPC methods used for runtime status, one-time invite links, SimpleX addresses, contact requests, group links, and operator-controlled SimpleX onboarding. --- -The plugin registers these gateway RPC methods for invite/address automation and programmatic onboarding flows. +The plugin registers these gateway RPC methods for runtime diagnostics, one-time invite and SimpleX address automation, contact-request review, group-link lifecycle, and operator-controlled SimpleX link onboarding. - For day-to-day operator workflows, prefer the plugin CLI commands in [Example Commands](/reference/example-commands). Use these gateway methods when another system needs the same invite/address flow programmatically, including QR data URLs. + For day-to-day one-time invite and SimpleX address workflows, prefer the plugin CLI commands in [Example Commands](/reference/example-commands). Use these gateway methods when another system needs programmatic access to runtime status, contact-request review, group-link lifecycle, or link onboarding. ## Prerequisites and behavior @@ -18,7 +18,7 @@ The plugin registers these gateway RPC methods for invite/address automation and ## `simplex.invite.create` -Create a one-time connect link or an address link. +Create a one-time connect link or return/create the account's reusable SimpleX address. Request params: @@ -31,8 +31,8 @@ Request params: `mode` supports: -- `connect` -> runs /c -- `address` -> runs /ad +- `connect` -> creates a one-time invitation link +- `address` -> returns or creates the account's reusable SimpleX address Response highlights: @@ -44,7 +44,7 @@ Example response shape: ```json { "accountId": "default", - "command": "/c", + "operation": "create-link", "mode": "connect", "link": "simplex://...", "qrDataUrl": "data:image/png;base64,..." @@ -53,7 +53,7 @@ Example response shape: ## `simplex.invite.list` -List current invite/address links and pending contact hints. Internally this combines /show_address and /contacts. +List the current SimpleX address and related contact state through the SimpleX runtime APIs. Request params: @@ -65,9 +65,9 @@ Request params: Response highlights: -- `addressLink`: current address link if available -- `links`: extracted invite/address links seen in the responses -- `pendingHints`: pending contact hints from `/contacts` +- `addressLink`: current SimpleX address link if available +- `links`: extracted SimpleX address links seen in the responses +- `pendingHints`: pending contact hints, if exposed by the runtime - `addressQrDataUrl`: PNG QR image as a `data:` URL for `addressLink` Example response shape: @@ -84,7 +84,7 @@ Example response shape: ## `simplex.invite.revoke` -Revoke the active address link by running /delete_address. +Revoke the active SimpleX address through the SimpleX runtime API. Request params: @@ -99,8 +99,121 @@ Example response shape: ```json { "accountId": "default", - "response": { - "corrId": "..." + "revoked": true +} +``` + +## `simplex.runtime.status` + +Return runtime and account state for automation and Control UI surfaces. + +Response highlights: + +- `activeUser`: active SimpleX user profile, if available +- `address`: current SimpleX address metadata, if available +- `counts`: contact, group, and user counts +- `runtime`: active client, connection state, and latest runtime error +- `runtimeVersion`: installed `simplex-chat` runtime version, if available +- `wsUrl`: configured WebSocket URL + +Example response shape: + +```json +{ + "accountId": "default", + "configured": true, + "mode": "external", + "wsUrl": "ws://127.0.0.1:5225", + "runtimeVersion": "6.5.0", + "runtime": { + "activeClient": true, + "connected": true, + "lastError": null + }, + "counts": { + "contacts": 1, + "groups": 0, + "users": 1 + } +} +``` + +## `simplex.runtime.doctor` + +Return the same runtime status plus `ok` and `issues` fields for operator diagnostics. + +## `simplex.requests.*` + +Manage pending SimpleX contact requests observed from runtime events. + +- `simplex.requests.list`: list stored pending requests for an account +- `simplex.requests.accept`: accept a request by `contactRequestId` +- `simplex.requests.reject`: reject a request by `contactRequestId` + +Example request: + +```json +{ + "accountId": "default", + "contactRequestId": 123 +} +``` + +## `simplex.groups.*` + +Create SimpleX groups and manage group invite links through the configured SimpleX WebSocket runtime. + +- `simplex.groups.create`: create a group from `displayName`, optional `fullName`, and optional `description` +- `simplex.groups.link.create`: create a group link for `groupId` and optional `role` +- `simplex.groups.link.list`: return the current group link plus QR data URL +- `simplex.groups.link.revoke`: delete the current group link + +Example group link request: + +```json +{ + "accountId": "default", + "groupId": 42, + "role": "member" +} +``` + +Example response shape: + +```json +{ + "accountId": "default", + "groupId": 42, + "role": "member", + "link": "simplex://...", + "qrDataUrl": "data:image/png;base64,..." +} +``` + +## `simplex.connect.*` + +Let an operator connect the SimpleX runtime identity to a SimpleX contact/address/group link. + +- `simplex.connect.plan`: inspect what connecting to `link` would do +- `simplex.connect`: connect the active SimpleX user to `link` + +Example request: + +```json +{ + "accountId": "default", + "link": "simplex://..." +} +``` + +Example response shape: + +```json +{ + "accountId": "default", + "connected": true, + "result": { + "type": "sentConfirmation" } } ``` @@ -110,7 +223,7 @@ Example response shape: | Code | Meaning | | --- | --- | | `INVALID_REQUEST` | The request params are invalid. For example, simplex.invite.create received an unsupported mode. | -| `UNAVAILABLE` | The account is disabled/unconfigured, the channel runtime is unavailable, or the underlying SimpleX command failed. The response message includes the failure reason. | +| `UNAVAILABLE` | The account is disabled/unconfigured, the channel runtime is unavailable, or the underlying SimpleX runtime API call failed. The response message includes the failure reason. | ## Related agent tools diff --git a/docs/reference/screenshots.mdx b/docs/reference/screenshots.mdx index 8959e6d..94a751f 100644 --- a/docs/reference/screenshots.mdx +++ b/docs/reference/screenshots.mdx @@ -15,7 +15,7 @@ Generate a one-time invite link with the plugin CLI: openclaw simplex invite create --qr ``` -You can also generate or inspect links directly in the simplex-chat console with /c, /ad, or /show_address. For automation, OpenClaw also exposes simplex.invite.create and simplex.invite.list. +You can also generate or inspect links directly in the simplex-chat console with /c, /ad, or /show_address. For automation, OpenClaw also exposes gateway methods for invites, runtime status, contact requests, group links, and link onboarding. diff --git a/examples/caddy/Caddyfile b/examples/caddy/Caddyfile new file mode 100644 index 0000000..d855820 --- /dev/null +++ b/examples/caddy/Caddyfile @@ -0,0 +1,14 @@ +# Remote WebSocket access should be exceptional. Prefer loopback or a private +# sidecar network. If remote access is unavoidable, terminate TLS and require +# authentication at the proxy because simplex-chat's WebSocket API has no +# native authentication. + +simplex.example.com { + encode zstd gzip + + basicauth { + openclaw $2a$14$REPLACE_WITH_CADDY_HASHED_PASSWORD + } + + reverse_proxy 127.0.0.1:5225 +} diff --git a/examples/docker-compose.sidecar.yml b/examples/docker-compose.sidecar.yml new file mode 100644 index 0000000..216f355 --- /dev/null +++ b/examples/docker-compose.sidecar.yml @@ -0,0 +1,57 @@ +# Hardened sidecar example for the external-runtime path. +# +# The important property is that simplex-chat is not published to the host or +# internet. OpenClaw reaches it over a compose-only service name as +# ws://simplex-chat:5225. Do not add a `ports:` mapping for simplex-chat. +# The plugin requires allowUnsafeRemoteWs because Docker service names are +# non-loopback even though the port is not published to the host. + +services: + openclaw: + image: ghcr.io/openclaw/openclaw:latest + restart: unless-stopped + depends_on: + simplex-chat: + condition: service_healthy + environment: + OPENCLAW_CONFIG: /config/openclaw.json + volumes: + - ./openclaw:/config + - openclaw-state:/state + networks: + - openclaw-private + + simplex-chat: + build: + context: ./docker/simplex-chat-runtime + restart: unless-stopped + command: + - simplex-chat + - -p + - "5225" + - --device-name + - OpenClaw SimpleX + - --files-folder + - /var/lib/simplex/files + - --temp-folder + - /var/lib/simplex/tmp + volumes: + - simplex-state:/var/lib/simplex + expose: + - "5225" + healthcheck: + test: + - CMD-SHELL + - "nc -z 127.0.0.1 5225" + interval: 10s + timeout: 3s + retries: 12 + networks: + - openclaw-private + +networks: + openclaw-private: + +volumes: + openclaw-state: + simplex-state: diff --git a/examples/docker/simplex-chat-runtime/Dockerfile b/examples/docker/simplex-chat-runtime/Dockerfile new file mode 100644 index 0000000..6039be8 --- /dev/null +++ b/examples/docker/simplex-chat-runtime/Dockerfile @@ -0,0 +1,13 @@ +FROM debian:bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl netcat-openbsd \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://raw.githubusercontent.com/simplex-chat/simplex-chat/stable/install.sh | bash + +RUN mkdir -p /var/lib/simplex/files /var/lib/simplex/tmp + +WORKDIR /var/lib/simplex + +CMD ["simplex-chat", "-p", "5225", "--device-name", "OpenClaw SimpleX", "--files-folder", "/var/lib/simplex/files", "--temp-folder", "/var/lib/simplex/tmp"] diff --git a/examples/systemd/simplex-chat.service b/examples/systemd/simplex-chat.service new file mode 100644 index 0000000..cee01c2 --- /dev/null +++ b/examples/systemd/simplex-chat.service @@ -0,0 +1,21 @@ +[Unit] +Description=SimpleX Chat WebSocket Runtime for OpenClaw +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=%h/.local/bin/simplex-chat -p 5225 --device-name "OpenClaw SimpleX" --files-folder %h/.local/state/openclaw-simplex/files --temp-folder %h/.local/state/openclaw-simplex/tmp --log-file %h/.local/state/openclaw-simplex/simplex-chat.log +Restart=on-failure +RestartSec=5 +StartLimitIntervalSec=60 +StartLimitBurst=10 + +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=%h/.simplex %h/.local/state/openclaw-simplex + +[Install] +WantedBy=default.target diff --git a/index.test.ts b/index.test.ts index c78dc36..1aa1813 100644 --- a/index.test.ts +++ b/index.test.ts @@ -9,10 +9,6 @@ function setMockResponse(next: MockResponse | MockResponse[]): void { mockSendResponses = Array.isArray(next) ? [...next] : [next]; } -function getLastCommand(): string | null { - return sentCommands[sentCommands.length - 1] ?? null; -} - function getCommands(): string[] { return [...sentCommands]; } @@ -22,26 +18,83 @@ function resetMockState(): void { mockSendResponses = [{ resp: { type: "ok" } }]; } -const qrMocks = vi.hoisted(() => ({ - toDataURL: vi.fn(async () => "data:image/png;base64,mock-base64"), -})); +function nextMockPayload(): MockResponse { + const next = mockSendResponses.shift(); + return (next && "resp" in next ? next.resp : (next ?? { type: "ok" })) as MockResponse; +} -vi.mock("./src/simplex/simplex-ws-client.js", () => ({ - SimplexWsClient: class { +function extractMockLink(value: unknown): string | null { + if (typeof value === "string") { + return value.match(/simplex:\/\/\S+/)?.[0] ?? null; + } + if (!value || typeof value !== "object") { + return null; + } + const record = value as Record; + return extractMockLink(record.output) ?? extractMockLink(record.message); +} + +vi.mock("./src/simplex/runtime/client.js", () => ({ + SimplexClient: class { async connect() {} - async sendCommand(cmd: string) { - sentCommands.push(cmd); - const next = mockSendResponses.shift(); - return next ?? { resp: { type: "ok" } }; + getConnectionState() { + return { connected: true, at: 1, error: null }; + } + async getActiveUser() { + return { userId: 1 }; + } + async listUsers() { + return [{ user: { userId: 1 } }]; + } + async createInviteLink() { + return { link: extractMockLink(nextMockPayload()) ?? "simplex://invite-mock", response: {} }; + } + async getAddress() { + const payload = nextMockPayload(); + return { link: extractMockLink(payload), response: payload }; + } + async createAddress() { + const payload = nextMockPayload(); + return { link: extractMockLink(payload) ?? "simplex://address-mock", response: payload }; } + async deleteAddress() {} + async listContacts() { + return [nextMockPayload()]; + } + async listGroups() { + return [{ groupId: 10 }]; + } + async createGroup() { + return { groupId: 11, groupProfile: { displayName: "Ops" } }; + } + async createGroupLink() { + return { link: "simplex://group-link", response: {} }; + } + async getGroupLink() { + return { link: "simplex://group-link", response: { groupLinkId: "group-link" } }; + } + async deleteGroupLink() {} + async planConnect() { + return { type: "ok", link: "simplex://x" }; + } + async connectLink() { + return { type: "sentConfirmation" }; + } + async acceptContactRequest() { + return { contactId: 100 }; + } + async rejectContactRequest() {} + async sendMessages() { + return [{ chatItem: { meta: { itemId: 1 } } }]; + } + async receiveFile() { + return { chatItem: { meta: { itemId: 1 } } }; + } + async cancelFile() {} async close() {} }, })); -vi.mock("qrcode", () => ({ - toDataURL: qrMocks.toDataURL, -})); - import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/channel-core"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import plugin from "./index.js"; @@ -68,13 +121,6 @@ const noopLogger = { type Handler = (ctx: { params?: Record; respond: (ok: boolean, payload?: unknown, err?: unknown) => void; - context: { - startChannel: (channel: string, accountId?: string) => Promise; - getRuntimeSnapshot: () => { - channels?: Record; - channelAccounts?: Record>; - }; - }; }) => Promise; type BeforeToolCallHook = (event: { toolName: string; params: Record }) => unknown; @@ -132,11 +178,14 @@ function setupRegistration( }, registerReload: () => {}, registerNodeHostCommand: () => {}, + registerNodeInvokePolicy: () => {}, registerSecurityAuditCollector: () => {}, registerService: () => {}, + registerGatewayDiscoveryService: () => {}, registerCliBackend: () => {}, registerTextTransforms: () => {}, registerConfigMigration: () => {}, + registerMigrationProvider: () => {}, registerAutoEnableProbe: () => {}, registerProvider: () => {}, registerSpeechProvider: () => {}, @@ -153,6 +202,23 @@ function setupRegistration( registerContextEngine: () => {}, registerCompactionProvider: () => {}, registerAgentHarness: () => {}, + registerCodexAppServerExtensionFactory: () => {}, + registerAgentToolResultMiddleware: () => {}, + registerSessionExtension: () => {}, + enqueueNextTurnInjection: async (injection) => ({ + enqueued: false, + id: "mock-injection", + sessionKey: injection.sessionKey, + }), + registerTrustedToolPolicy: () => {}, + registerToolMetadata: () => {}, + registerControlUiDescriptor: () => {}, + registerRuntimeLifecycle: () => {}, + registerAgentEventSubscription: () => {}, + setRunContext: () => false, + getRunContext: () => undefined, + clearRunContext: () => {}, + registerSessionSchedulerJob: () => undefined, registerDetachedTaskRuntime: () => {}, registerMemoryCapability: () => {}, registerMemoryPromptSupplement: () => {}, @@ -162,8 +228,6 @@ function setupRegistration( registerMemoryEmbeddingProvider: () => {}, registerMemoryPromptSection: () => {}, registerCommand: () => {}, - registerEmbeddedExtensionFactory: () => {}, - registerCodexAppServerExtensionFactory: () => {}, on: (hookName, handler) => { hooks.push({ events: hookName, handler }); }, @@ -204,11 +268,26 @@ describe("plugin entry registration modes", () => { expect(full.methods.has("simplex.invite.create")).toBe(true); expect(full.methods.has("simplex.invite.list")).toBe(true); expect(full.methods.has("simplex.invite.revoke")).toBe(true); + expect(full.methods.has("simplex.runtime.status")).toBe(true); + expect(full.methods.has("simplex.requests.accept")).toBe(true); + expect(full.methods.has("simplex.groups.link.create")).toBe(true); + expect(full.methods.has("simplex.connect")).toBe(true); expect(full.methodScopes).toEqual( new Map([ ["simplex.invite.create", "operator.write"], ["simplex.invite.list", "operator.read"], ["simplex.invite.revoke", "operator.admin"], + ["simplex.runtime.status", "operator.read"], + ["simplex.runtime.doctor", "operator.read"], + ["simplex.requests.list", "operator.read"], + ["simplex.requests.accept", "operator.admin"], + ["simplex.requests.reject", "operator.admin"], + ["simplex.groups.create", "operator.admin"], + ["simplex.groups.link.create", "operator.admin"], + ["simplex.groups.link.list", "operator.read"], + ["simplex.groups.link.revoke", "operator.admin"], + ["simplex.connect.plan", "operator.read"], + ["simplex.connect", "operator.admin"], ]) ); expect(setupOnly.methods.size).toBe(0); @@ -244,7 +323,6 @@ describe("plugin entry registration modes", () => { }); expect(simplexPlugin.heartbeat).toBeTruthy(); expect(typeof simplexPlugin.heartbeat?.checkReady).toBe("function"); - expect(typeof simplexPlugin.heartbeat?.resolveRecipients).toBe("function"); }); it("marks destructive simplex tools as owner-only", () => { @@ -334,7 +412,7 @@ describe("simplex channel config and allowlist adapters", () => { allowFrom: ["@base"], accounts: { ops: { - connection: { wsUrl: "ws://127.0.0.1:6225" }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, allowFrom: ["@ops"], }, }, @@ -498,10 +576,6 @@ describe("simplex invite gateway", () => { await handler({ params: { mode: "bad" }, respond, - context: { - startChannel: async () => {}, - getRuntimeSnapshot: () => ({ channels: {}, channelAccounts: {} }), - }, }); const firstCall = respond.mock.calls[0]; expect(firstCall).toBeDefined(); @@ -525,13 +599,6 @@ describe("simplex invite gateway", () => { await handler({ params: { mode: "connect" }, respond, - context: { - startChannel: async () => {}, - getRuntimeSnapshot: () => ({ - channels: { "openclaw-simplex": { running: false } }, - channelAccounts: {}, - }), - }, }); const firstCall = respond.mock.calls[0]; @@ -543,14 +610,13 @@ describe("simplex invite gateway", () => { expect(ok).toBe(true); expect(payload).toMatchObject({ link: "simplex://invite123", - qrDataUrl: "data:image/png;base64,mock-base64", mode: "connect", }); - expect(getLastCommand()).toBe("/c"); - expect(qrMocks.toDataURL).toHaveBeenCalledWith("simplex://invite123", expect.any(Object)); + expect((payload as { qrDataUrl?: string }).qrDataUrl).toMatch(/^data:image\/png;base64,/); + expect(getCommands()).toEqual([]); }); - it("uses address mode to build invite command", async () => { + it("uses address mode to create or return an address link", async () => { setMockResponse({ resp: { type: "ok", @@ -563,13 +629,6 @@ describe("simplex invite gateway", () => { await handler({ params: { mode: "address" }, respond, - context: { - startChannel: async () => {}, - getRuntimeSnapshot: () => ({ - channels: { "openclaw-simplex": { running: true } }, - channelAccounts: {}, - }), - }, }); const firstCall = respond.mock.calls[0]; @@ -583,7 +642,7 @@ describe("simplex invite gateway", () => { link: "simplex://address456", mode: "address", }); - expect(getLastCommand()).toBe("/ad"); + expect(getCommands()).toEqual([]); }); it("lists address links and pending hints", async () => { @@ -607,13 +666,6 @@ describe("simplex invite gateway", () => { await handler({ params: {}, respond, - context: { - startChannel: async () => {}, - getRuntimeSnapshot: () => ({ - channels: { "openclaw-simplex": { running: true } }, - channelAccounts: {}, - }), - }, }); const firstCall = respond.mock.calls[0]; @@ -627,11 +679,14 @@ describe("simplex invite gateway", () => { accountId: "default", addressLink: "simplex://address789", links: ["simplex://address789", "simplex://invite999"], - addressQrDataUrl: "data:image/png;base64,mock-base64", }); - expect((payload as { pendingHints?: string[] }).pendingHints?.length).toBeGreaterThan(0); - expect(getCommands()).toEqual(["/show_address", "/contacts"]); - expect(qrMocks.toDataURL).toHaveBeenCalledWith("simplex://address789", expect.any(Object)); + expect((payload as { addressQrDataUrl?: string }).addressQrDataUrl).toMatch( + /^data:image\/png;base64,/ + ); + expect((payload as { pendingHints?: string[] }).pendingHints).toEqual([ + "Pending contact request from Bob simplex://invite999", + ]); + expect(getCommands()).toEqual([]); }); it("revokes address link for selected account", async () => { @@ -640,7 +695,7 @@ describe("simplex invite gateway", () => { "openclaw-simplex": { accounts: { ops: { - connection: { wsUrl: "ws://127.0.0.1:7777", mode: "external" }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, }, }, }, @@ -650,13 +705,6 @@ describe("simplex invite gateway", () => { await handler({ params: { accountId: "ops" }, respond, - context: { - startChannel: async () => {}, - getRuntimeSnapshot: () => ({ - channels: { "openclaw-simplex": { running: true } }, - channelAccounts: { "openclaw-simplex": { ops: { running: true } } }, - }), - }, }); const firstCall = respond.mock.calls[0]; @@ -667,10 +715,10 @@ describe("simplex invite gateway", () => { const [ok, payload] = firstCall; expect(ok).toBe(true); expect(payload).toMatchObject({ accountId: "ops" }); - expect(getLastCommand()).toBe("/delete_address"); + expect(getCommands()).toEqual([]); }); - it("uses the configured default account runtime when accountId is omitted", async () => { + it("uses the configured default account when accountId is omitted", async () => { setMockResponse({ resp: { type: "ok", @@ -683,24 +731,16 @@ describe("simplex invite gateway", () => { "openclaw-simplex": { accounts: { ops: { - connection: { wsUrl: "ws://127.0.0.1:7777", mode: "external" }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, }, }, }, }, }); const respond = vi.fn(); - const startChannel = vi.fn(async () => {}); await handler({ params: { mode: "address" }, respond, - context: { - startChannel, - getRuntimeSnapshot: () => ({ - channels: { "openclaw-simplex": { running: false } }, - channelAccounts: { "openclaw-simplex": { ops: { running: true } } }, - }), - }, }); const firstCall = respond.mock.calls[0]; @@ -715,6 +755,95 @@ describe("simplex invite gateway", () => { mode: "address", link: "simplex://address999", }); - expect(startChannel).not.toHaveBeenCalled(); + }); + + it("returns runtime status through the gateway", async () => { + const handler = setupHandler("simplex.runtime.status", simplexConfiguredChannel); + const respond = vi.fn(); + await handler({ params: {}, respond }); + + const firstCall = respond.mock.calls[0]; + expect(firstCall).toBeDefined(); + if (!firstCall) { + throw new Error("missing response call"); + } + const [ok, payload] = firstCall; + expect(ok).toBe(true); + expect(payload).toMatchObject({ + accountId: "default", + mode: "external", + counts: { + contacts: 1, + groups: 1, + users: 1, + }, + }); + }); + + it("creates group links with QR data", async () => { + const handler = setupHandler("simplex.groups.link.create", simplexConfiguredChannel); + const respond = vi.fn(); + await handler({ + params: { groupId: 10, role: "member" }, + respond, + }); + + const firstCall = respond.mock.calls[0]; + expect(firstCall).toBeDefined(); + if (!firstCall) { + throw new Error("missing response call"); + } + const [ok, payload] = firstCall; + expect(ok).toBe(true); + expect(payload).toMatchObject({ + accountId: "default", + groupId: 10, + role: "member", + link: "simplex://group-link", + }); + expect((payload as { qrDataUrl?: string }).qrDataUrl).toMatch(/^data:image\/png;base64,/); + }); + + it("accepts pending contact requests by id", async () => { + const handler = setupHandler("simplex.requests.accept", simplexConfiguredChannel); + const respond = vi.fn(); + await handler({ + params: { contactRequestId: 7 }, + respond, + }); + + const firstCall = respond.mock.calls[0]; + expect(firstCall).toBeDefined(); + if (!firstCall) { + throw new Error("missing response call"); + } + const [ok, payload] = firstCall; + expect(ok).toBe(true); + expect(payload).toMatchObject({ + accountId: "default", + contactRequestId: 7, + accepted: true, + }); + }); + + it("connects to SimpleX links through the operator gateway", async () => { + const handler = setupHandler("simplex.connect", simplexConfiguredChannel); + const respond = vi.fn(); + await handler({ + params: { link: "simplex://contact-link" }, + respond, + }); + + const firstCall = respond.mock.calls[0]; + expect(firstCall).toBeDefined(); + if (!firstCall) { + throw new Error("missing response call"); + } + const [ok, payload] = firstCall; + expect(ok).toBe(true); + expect(payload).toMatchObject({ + accountId: "default", + connected: true, + }); }); }); diff --git a/index.ts b/index.ts index 404cdd7..bf2aa96 100644 --- a/index.ts +++ b/index.ts @@ -10,7 +10,7 @@ import { registerSimplexToolHooks, registerSimplexTools } from "./src/tools/plug const pluginEntry: ReturnType = defineChannelPluginEntry({ id: SIMPLEX_PLUGIN_ID, name: "SimpleX", - description: "SimpleX Chat channel plugin via external WebSocket API", + description: "SimpleX Chat channel plugin via the official Node runtime", plugin: simplexPlugin, setRuntime: setSimplexRuntime, registerCliMetadata: registerSimplexCliMetadata, diff --git a/openclaw-docs.png b/openclaw-docs.png deleted file mode 100644 index 3c99ff4..0000000 Binary files a/openclaw-docs.png and /dev/null differ diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 668d5b4..0c3d3f6 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -3,6 +3,7 @@ "kind": "channel", "channels": ["openclaw-simplex"], "activation": { + "onStartup": false, "onChannels": ["openclaw-simplex"], "onCapabilities": ["channel", "tool"] }, @@ -18,7 +19,7 @@ ] }, "name": "SimpleX", - "description": "SimpleX Chat channel plugin via external WebSocket API", + "description": "SimpleX channel plugin for OpenClaw using an external WebSocket runtime", "configSchema": { "type": "object", "additionalProperties": false, @@ -76,6 +77,10 @@ "minimum": 0, "maximum": 9007199254740991 }, + "contextVisibility": { + "type": "string", + "enum": ["all", "allowlist", "allowlist_quote"] + }, "dms": { "type": "object", "propertyNames": { @@ -189,6 +194,9 @@ "exclusiveMinimum": 0, "maximum": 9007199254740991 }, + "allowUnsafeRemoteWs": { + "type": "boolean" + }, "autoAcceptFiles": { "type": "boolean" }, @@ -252,6 +260,10 @@ "minimum": 0, "maximum": 9007199254740991 }, + "contextVisibility": { + "type": "string", + "enum": ["all", "allowlist", "allowlist_quote"] + }, "dms": { "type": "object", "propertyNames": { @@ -365,6 +377,9 @@ "exclusiveMinimum": 0, "maximum": 9007199254740991 }, + "allowUnsafeRemoteWs": { + "type": "boolean" + }, "autoAcceptFiles": { "type": "boolean" }, @@ -389,7 +404,7 @@ "uiHints": { "": { "label": "SimpleX", - "help": "Connect OpenClaw to a separately running simplex-chat WebSocket runtime. Reachability starts from SimpleX invite or address links, while OpenClaw still applies pairing, allowlists, and group policy." + "help": "Connect OpenClaw to SimpleX through an external simplex-chat WebSocket runtime. Reachability starts from SimpleX invite or address links, while OpenClaw still applies pairing, allowlists, and group policy." }, "defaultAccount": { "label": "Default Account", @@ -416,6 +431,88 @@ "label": "Enabled", "help": "Disable an account without deleting its SimpleX configuration." }, + "connection": { + "label": "WebSocket Runtime", + "help": "Connection settings for the external simplex-chat WebSocket runtime.", + "tags": ["transport"] + }, + "accounts.*.connection": { + "label": "WebSocket Runtime", + "help": "Connection settings for the external simplex-chat WebSocket runtime.", + "tags": ["transport"] + }, + "connection.wsUrl": { + "label": "WebSocket URL", + "help": "WebSocket URL for the external simplex-chat runtime.", + "placeholder": "ws://127.0.0.1:5225", + "tags": ["transport"] + }, + "accounts.*.connection.wsUrl": { + "label": "WebSocket URL", + "help": "WebSocket URL for the external simplex-chat runtime.", + "placeholder": "ws://127.0.0.1:5225", + "tags": ["transport"] + }, + "connection.wsHost": { + "label": "WebSocket Host", + "help": "Host used to build the runtime WebSocket URL when wsUrl is omitted.", + "placeholder": "127.0.0.1", + "advanced": true + }, + "accounts.*.connection.wsHost": { + "label": "WebSocket Host", + "help": "Host used to build the runtime WebSocket URL when wsUrl is omitted.", + "placeholder": "127.0.0.1", + "advanced": true + }, + "connection.wsPort": { + "label": "WebSocket Port", + "help": "Port used to build the runtime WebSocket URL when wsUrl is omitted.", + "placeholder": "5225", + "advanced": true + }, + "accounts.*.connection.wsPort": { + "label": "WebSocket Port", + "help": "Port used to build the runtime WebSocket URL when wsUrl is omitted.", + "placeholder": "5225", + "advanced": true + }, + "connection.allowUnsafeRemoteWs": { + "label": "Allow Unsafe Remote WS", + "help": "Allow plaintext ws:// connections to non-loopback hosts only when protected by a private network, firewall, or TLS proxy.", + "advanced": true, + "tags": ["transport", "security"] + }, + "accounts.*.connection.allowUnsafeRemoteWs": { + "label": "Allow Unsafe Remote WS", + "help": "Allow plaintext ws:// connections to non-loopback hosts only when protected by a private network, firewall, or TLS proxy.", + "advanced": true, + "tags": ["transport", "security"] + }, + "connection.autoAcceptFiles": { + "label": "Auto Accept Files", + "help": "Automatically accept incoming file transfers from the SimpleX runtime.", + "advanced": true, + "tags": ["media"] + }, + "accounts.*.connection.autoAcceptFiles": { + "label": "Auto Accept Files", + "help": "Automatically accept incoming file transfers from the SimpleX runtime.", + "advanced": true, + "tags": ["media"] + }, + "connection.connectTimeoutMs": { + "label": "Connect Timeout (ms)", + "help": "Runtime connection/start timeout in milliseconds.", + "advanced": true, + "tags": ["transport"] + }, + "accounts.*.connection.connectTimeoutMs": { + "label": "Connect Timeout (ms)", + "help": "Runtime connection/start timeout in milliseconds.", + "advanced": true, + "tags": ["transport"] + }, "mediaMaxMb": { "label": "Max Media Size (MB)", "help": "Optional media size cap for files sent through this SimpleX account.", @@ -428,6 +525,52 @@ "advanced": true, "tags": ["media"] }, + "actions": { + "label": "Actions", + "help": "Optional per-account action toggles for SimpleX message features.", + "advanced": true + }, + "accounts.*.actions": { + "label": "Actions", + "help": "Optional per-account action toggles for SimpleX message features.", + "advanced": true + }, + "actions.reactions": { + "label": "Enable Reactions", + "help": "Allow the agent to add or remove emoji reactions through the SimpleX runtime.", + "advanced": true, + "tags": ["actions"] + }, + "accounts.*.actions.reactions": { + "label": "Enable Reactions", + "help": "Allow the agent to add or remove emoji reactions through the SimpleX runtime.", + "advanced": true, + "tags": ["actions"] + }, + "actions.polls": { + "label": "Enable Polls", + "help": "Allow the agent to send OpenClaw poll prompts into SimpleX chats.", + "advanced": true, + "tags": ["actions"] + }, + "accounts.*.actions.polls": { + "label": "Enable Polls", + "help": "Allow the agent to send OpenClaw poll prompts into SimpleX chats.", + "advanced": true, + "tags": ["actions"] + }, + "reactionLevel": { + "label": "Reaction Level", + "help": "Controls agent reaction behavior. Use \"minimal\" for restrained reactions, \"extensive\" for more expressive use, \"ack\" for non-agent acknowledgements only, and \"off\" to disable reactions entirely.", + "advanced": true, + "tags": ["actions"] + }, + "accounts.*.reactionLevel": { + "label": "Reaction Level", + "help": "Controls agent reaction behavior. Use \"minimal\" for restrained reactions, \"extensive\" for more expressive use, \"ack\" for non-agent acknowledgements only, and \"off\" to disable reactions entirely.", + "advanced": true, + "tags": ["actions"] + }, "dmPolicy": { "label": "DM Policy", "help": "Direct-message access policy. \"pairing\" is the safest default; \"open\" should stay paired with a deliberate allowFrom policy.", @@ -448,6 +591,18 @@ "help": "Optional direct-message transcript window to retain for reply context.", "advanced": true }, + "contextVisibility": { + "label": "Context Visibility", + "help": "Controls whether supplemental chat context is visible broadly or only for allowlisted senders.", + "advanced": true, + "tags": ["security"] + }, + "accounts.*.contextVisibility": { + "label": "Context Visibility", + "help": "Controls whether supplemental chat context is visible broadly or only for allowlisted senders.", + "advanced": true, + "tags": ["security"] + }, "allowFrom": { "label": "DM Allowlist", "help": "Allowed SimpleX senders for direct messages. Use [\"*\"] only when you intentionally want broad DM reachability.", @@ -557,134 +712,6 @@ "label": "Tool Policy", "help": "Optional per-group tool policy override for this SimpleX group.", "advanced": true - }, - "connection": { - "label": "Connection", - "help": "Transport settings for the external simplex-chat WebSocket runtime." - }, - "accounts.*.connection": { - "label": "Connection", - "help": "Transport settings for the external simplex-chat WebSocket runtime." - }, - "connection.mode": { - "label": "Connection Mode", - "help": "SimpleX currently supports only the external runtime mode in this plugin.", - "advanced": true - }, - "accounts.*.connection.mode": { - "label": "Connection Mode", - "help": "SimpleX currently supports only the external runtime mode in this plugin.", - "advanced": true - }, - "connection.wsUrl": { - "label": "WebSocket URL", - "help": "WebSocket URL for the running simplex-chat API, for example ws://127.0.0.1:5225.", - "placeholder": "ws://127.0.0.1:5225", - "tags": ["transport"] - }, - "accounts.*.connection.wsUrl": { - "label": "WebSocket URL", - "help": "WebSocket URL for the running simplex-chat API, for example ws://127.0.0.1:5225.", - "placeholder": "ws://127.0.0.1:5225", - "tags": ["transport"] - }, - "connection.wsHost": { - "label": "WebSocket Host", - "help": "Host override used when wsUrl is not set explicitly.", - "placeholder": "127.0.0.1", - "advanced": true, - "tags": ["transport"] - }, - "accounts.*.connection.wsHost": { - "label": "WebSocket Host", - "help": "Host override used when wsUrl is not set explicitly.", - "placeholder": "127.0.0.1", - "advanced": true, - "tags": ["transport"] - }, - "connection.wsPort": { - "label": "WebSocket Port", - "help": "Port override used when wsUrl is not set explicitly.", - "placeholder": "5225", - "advanced": true, - "tags": ["transport"] - }, - "accounts.*.connection.wsPort": { - "label": "WebSocket Port", - "help": "Port override used when wsUrl is not set explicitly.", - "placeholder": "5225", - "advanced": true, - "tags": ["transport"] - }, - "connection.autoAcceptFiles": { - "label": "Auto Accept Files", - "help": "Automatically accept incoming file transfers from the SimpleX runtime.", - "advanced": true, - "tags": ["media"] - }, - "accounts.*.connection.autoAcceptFiles": { - "label": "Auto Accept Files", - "help": "Automatically accept incoming file transfers from the SimpleX runtime.", - "advanced": true, - "tags": ["media"] - }, - "connection.connectTimeoutMs": { - "label": "Connect Timeout (ms)", - "help": "WebSocket connection timeout in milliseconds.", - "advanced": true, - "tags": ["transport"] - }, - "accounts.*.connection.connectTimeoutMs": { - "label": "Connect Timeout (ms)", - "help": "WebSocket connection timeout in milliseconds.", - "advanced": true, - "tags": ["transport"] - }, - "actions": { - "label": "Actions", - "help": "Optional per-account action toggles for SimpleX message features.", - "advanced": true - }, - "accounts.*.actions": { - "label": "Actions", - "help": "Optional per-account action toggles for SimpleX message features.", - "advanced": true - }, - "actions.reactions": { - "label": "Enable Reactions", - "help": "Allow the agent to add or remove emoji reactions through the SimpleX runtime.", - "advanced": true, - "tags": ["actions"] - }, - "accounts.*.actions.reactions": { - "label": "Enable Reactions", - "help": "Allow the agent to add or remove emoji reactions through the SimpleX runtime.", - "advanced": true, - "tags": ["actions"] - }, - "actions.polls": { - "label": "Enable Polls", - "help": "Allow the agent to send OpenClaw poll prompts into SimpleX chats.", - "advanced": true, - "tags": ["actions"] - }, - "accounts.*.actions.polls": { - "label": "Enable Polls", - "help": "Allow the agent to send OpenClaw poll prompts into SimpleX chats.", - "advanced": true, - "tags": ["actions"] - }, - "reactionLevel": { - "label": "Reaction Level", - "help": "Controls agent reaction behavior. Use \"minimal\" for restrained reactions, \"extensive\" for more expressive use, \"ack\" for non-agent acknowledgements only, and \"off\" to disable reactions entirely.", - "advanced": true, - "tags": ["actions"] - }, - "accounts.*.reactionLevel": { - "label": "Reaction Level", - "help": "Controls agent reaction behavior. Use \"minimal\" for restrained reactions, \"extensive\" for more expressive use, \"ack\" for non-agent acknowledgements only, and \"off\" to disable reactions entirely.", - "advanced": true, - "tags": ["actions"] } }, "label": "SimpleX", diff --git a/package.json b/package.json index e7f7f22..23630c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@dangoldbj/openclaw-simplex", - "version": "1.5.0", - "description": "SimpleX channel plugin for OpenClaw", + "version": "1.6.0", + "description": "SimpleX channel plugin for OpenClaw using an external WebSocket runtime", "homepage": "https://openclaw-simplex.mintlify.app/", "repository": { "type": "git", @@ -43,7 +43,7 @@ "manifest:sync": "pnpm run build && node scripts/write-plugin-manifest.mjs && biome format --write openclaw.plugin.json", "typecheck": "tsc --noEmit", "test": "vitest run", - "prepublishOnly": "pnpm run test && pnpm run typecheck && pnpm run build" + "prepublishOnly": "pnpm run check && pnpm run test && pnpm run typecheck && pnpm run build" }, "engines": { "node": ">=22" @@ -52,26 +52,34 @@ "@sinclair/typebox": "^0.34.49", "qrcode": "^1.5.4", "ws": "^8.20.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "peerDependencies": { - "openclaw": ">=2026.4.23" + "openclaw": ">=2026.5.4" }, "devDependencies": { - "@biomejs/biome": "^2.4.10", - "@mintlify/cli": "^4.0.1090", - "@types/node": "^25.5.2", + "@biomejs/biome": "^2.4.13", + "@mintlify/cli": "^4.0.1146", + "@types/node": "^25.6.0", "@types/qrcode": "^1.5.5", "@types/ws": "^8.18.1", - "openclaw": "^2026.4.23", - "tsup": "^8.5.0", - "typescript": "^5.9.3", - "vitest": "^4.1.2" + "openclaw": "^2026.5.4", + "tsup": "^8.5.1", + "typescript": "^6.0.3", + "vitest": "^4.1.5" }, "openclaw": { "extensions": [ "./dist/index.js" ], + "compat": { + "pluginApi": ">=2026.5.4", + "minGatewayVersion": "2026.5.4" + }, + "build": { + "openclawVersion": "2026.5.4", + "pluginSdkVersion": "2026.5.4" + }, "channel": { "id": "openclaw-simplex", "label": "SimpleX", @@ -90,6 +98,7 @@ "External WebSocket runtime" ], "markdownCapable": true, + "forceAccountBinding": true, "exposure": { "configured": true, "setup": true, @@ -100,7 +109,7 @@ "install": { "npmSpec": "@dangoldbj/openclaw-simplex", "defaultChoice": "npm", - "minHostVersion": ">=2026.4.23" + "minHostVersion": ">=2026.5.4" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7822b7d..6a3ba3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,18 +18,18 @@ importers: specifier: ^8.20.0 version: 8.20.0 zod: - specifier: ^4.3.6 - version: 4.3.6 + specifier: ^4.4.3 + version: 4.4.3 devDependencies: '@biomejs/biome': - specifier: ^2.4.10 - version: 2.4.10 + specifier: ^2.4.13 + version: 2.4.13 '@mintlify/cli': - specifier: ^4.0.1090 - version: 4.0.1090(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/node@25.5.2)(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@5.9.3)(yaml@2.8.3) + specifier: ^4.0.1146 + version: 4.0.1146(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/node@25.6.0)(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@6.0.3)(yaml@2.8.4) '@types/node': - specifier: ^25.5.2 - version: 25.5.2 + specifier: ^25.6.0 + version: 25.6.0 '@types/qrcode': specifier: ^1.5.5 version: 1.5.6 @@ -37,22 +37,22 @@ importers: specifier: ^8.18.1 version: 8.18.1 openclaw: - specifier: ^2026.4.23 - version: 2026.4.23(@napi-rs/canvas@0.1.97) + specifier: ^2026.5.4 + version: 2026.5.4(@types/express@5.0.6) tsup: - specifier: ^8.5.0 - version: 8.5.1(jiti@1.21.7)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.3) + specifier: ^8.5.1 + version: 8.5.1(jiti@1.21.7)(postcss@8.5.6)(typescript@6.0.3)(yaml@2.8.4) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 vitest: - specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3)) + specifier: ^4.1.5 + version: 4.1.5(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4)) packages: - '@agentclientprotocol/sdk@0.19.0': - resolution: {integrity: sha512-U9I8ws9WTOk6jCBAWpXefGSDgVXn14/kV6HFzwWGcstQ02mOQgClMAROHmoIn9GqZbDBDEOkdIbP4P4TEMQdug==} + '@agentclientprotocol/sdk@0.21.0': + resolution: {integrity: sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw==} peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -64,8 +64,17 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@anthropic-ai/sdk@0.90.0': - resolution: {integrity: sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg==} + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@anthropic-ai/sdk@0.93.0': + resolution: {integrity: sha512-q9vaSZQVFx6B/gPxetGYfLXSJD5v0sOmh0OpZDq7yCrTSA+Rscvrtyol7JJTW40wEpQB4U1B4JXzxQitbQ3CAA==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -79,15 +88,9 @@ packages: '@ark/schema@0.55.0': resolution: {integrity: sha512-IlSIc0FmLKTDGr4I/FzNHauMn0MADA6bCjT1wauu4k6MyxhC1R9gz0olNpIRvK7lGGDwtc/VO0RUDNvVQW5WFg==} - '@ark/schema@0.56.0': - resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} - '@ark/util@0.55.0': resolution: {integrity: sha512-aWFNK7aqSvqFtVsl1xmbTjGbg91uqtJV7Za76YGNEwIO4qLjMfyY8flmmbhooYMuqPCO2jyxu8hve943D+w3bA==} - '@ark/util@0.56.0': - resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} - '@asyncapi/parser@3.4.0': resolution: {integrity: sha512-Sxn74oHiZSU6+cVeZy62iPZMFMvKp4jupMFHelSICCMw1qELmUHPvuZSr+ZHDmNGgHcEpzJM5HN02kR7T4g+PQ==} @@ -111,44 +114,60 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-bedrock-runtime@3.1035.0': - resolution: {integrity: sha512-XELIQk+znh53J2Bj0EmOftgcKRLw3tvI/P4WHLgSbSBWPh+wg0SvHu+bgIlzMHARbOC9auA0lsH+Eb9JwF1yjA==} + '@aws-sdk/client-bedrock-runtime@3.1042.0': + resolution: {integrity: sha512-uYJ/HDSQvorlgYqZSwRFGolEx5wygqyuBRfemXJ3Bla2yiRj9maSVOvWP88i/hDC2BKoH6NQw8GPB9Z4RYAnwQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-bedrock@3.1042.0': + resolution: {integrity: sha512-oEVjGU8wgW+eTF7ApdRU4jTs/iMVl4OdfpLmiNLuB082UVxxN/fQ5GIX2Ktbyt+x0mPlI3fug36XnOyf7oCo+Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-cognito-identity@3.1042.0': + resolution: {integrity: sha512-Cdan/gdzcNEwnvaxzCDQUDJnAAROfvnxjlv5/kvc78E1yQYdgqMN9vEzaqePDNjj8V0rel/iGx2tTlf3KY+V5g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.8': + resolution: {integrity: sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-cognito-identity@3.972.31': + resolution: {integrity: sha512-W5JtzDp3ejzhOOknXlnt+vJsNN2GZdAcBK+hR7HQ1DCacXqS0UpmnIyihIU7CK0IB+XYWeBaN3bBv4pXavp7Vg==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.974.4': - resolution: {integrity: sha512-EbVgyzQ83/Lf6oh1O4vYY47tuYw3Aosthh865LNU77KyotKz+uvEBNmsl/bSVS/vG+IU39mCqcOHrnhmhF4lug==} + '@aws-sdk/credential-provider-env@3.972.34': + resolution: {integrity: sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.30': - resolution: {integrity: sha512-dHpeqa29a0cBYq/h59IC2EK3AphLY96nKy4F35kBtiz9GuKDc32UYRTgjZaF8uuJCnqgw9omUZKR+9myyDHC2A==} + '@aws-sdk/credential-provider-http@3.972.36': + resolution: {integrity: sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.32': - resolution: {integrity: sha512-A+ZTT//Mswkf9DFEM6XlngwOtYdD8X4CUcoZ2wdpgI8cCs9mcGeuhgTwbGJvealub/MeONOaUr3FbRPMKmTDjg==} + '@aws-sdk/credential-provider-ini@3.972.38': + resolution: {integrity: sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.34': - resolution: {integrity: sha512-MoRc7tLnx3JpFkV2R826enEfBUVN8o9Cc7y3hnbMwiWzL/VJhgfxRQzHkEL9vWorMWP7tibltsRcLoid9fsVdw==} + '@aws-sdk/credential-provider-login@3.972.38': + resolution: {integrity: sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.34': - resolution: {integrity: sha512-XVSklkRRQ/CQDmv3VVFdZRl5hTFgncFhZrLyi0Ai4LZk5o3jpY5HIfuTK7ad7tixPKa+iQmL9+vg9qNyYZB+nw==} + '@aws-sdk/credential-provider-node@3.972.39': + resolution: {integrity: sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.35': - resolution: {integrity: sha512-nVrY7AdGfzYgAa/jd9m06p3ES7QQDaB7zN9c+vXnVXxBRkAs9MjRDPB5AKogWuC6phddltfvHGFqLDJmyU9u/A==} + '@aws-sdk/credential-provider-process@3.972.34': + resolution: {integrity: sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.30': - resolution: {integrity: sha512-McJPomNTSEo+C6UA3Zq6pFrcyTUaVsoPPBOvbOHAoIFPc8Z2CMLndqFJOnB+9bVFiBTWQLutlVGmrocBbvv4MQ==} + '@aws-sdk/credential-provider-sso@3.972.38': + resolution: {integrity: sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.34': - resolution: {integrity: sha512-WngYb2K+/yhkDOmDfAOjoCa9Ja3he0DZiAraboKwgWoVRkajDIcDYBCVbUTxtTUldvQoe7VvHLTrBNxvftN1aQ==} + '@aws-sdk/credential-provider-web-identity@3.972.38': + resolution: {integrity: sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.34': - resolution: {integrity: sha512-5KLUH+XmSNRj6amJiJSrPsCxU5l/PYDfxyqPa1MxWhHoQC3sxvGPrSib3IE+HQlfRA4e2kO0bnJy7HJdjvpuuA==} + '@aws-sdk/credential-providers@3.1042.0': + resolution: {integrity: sha512-TKOJXoBKTtfWkRrMITPBq4CFfiXydKc7bcvTONLhXgs1BuyHPcVp2KL0Rl1//xmlw48lYgnT/jn99SanLhB8EQ==} engines: {node: '>=20.0.0'} '@aws-sdk/eventstream-handler-node@3.972.14': @@ -171,32 +190,36 @@ packages: resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.33': - resolution: {integrity: sha512-n8Eh/+kq3u/EodLr8n6sQupu03QGjf122RHXCTGLaHSkavz/2beSKpRlq2oDgfmJZNkAkWF113xbyaUmyOd+YA==} + '@aws-sdk/middleware-sdk-s3@3.972.37': + resolution: {integrity: sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-user-agent@3.972.34': - resolution: {integrity: sha512-jrmJHyYlTQocR7H4VhvSFhaoedMb2rmlOTvFWD6tNBQ/EVQhTsrNfQUYFuPiOc2wUGxbm5LgCHtnvVmCPgODHw==} + '@aws-sdk/middleware-user-agent@3.972.38': + resolution: {integrity: sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-websocket@3.972.16': resolution: {integrity: sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==} engines: {node: '>= 14.0.0'} - '@aws-sdk/nested-clients@3.997.2': - resolution: {integrity: sha512-uGGQO08YetrqfInOKG5atRMrCDRQWRuZ9gGfKY6svPmuE4K7ac+XcbCkpWpjcA7yCYsBaKB/Nly4XKgPXUO1PA==} + '@aws-sdk/nested-clients@3.997.6': + resolution: {integrity: sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==} engines: {node: '>=20.0.0'} '@aws-sdk/region-config-resolver@3.972.13': resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.21': - resolution: {integrity: sha512-3EpT+C0QdmTMB5aVeJ5odWSLt9vg2oGzUXl1xvUazKGlkr9OBYnegNWqhhjGgZdv8RmSi5eS8nqqB+euNP2aqA==} + '@aws-sdk/signature-v4-multi-region@3.996.25': + resolution: {integrity: sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1041.0': + resolution: {integrity: sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1035.0': - resolution: {integrity: sha512-E6IO3Cn+OzBe6Sb5pnubd5Y8qSUMAsVKkD5QSwFfIx5fV1g5SkYwUDRDyPlm90RuIVcCo28wpMJU6W8wXH46Aw==} + '@aws-sdk/token-providers@3.1042.0': + resolution: {integrity: sha512-rOEGTVOrceb/1CfIWK0zl1v2WS70f/i5bDirLl5xdFAbVQ5znub6Ezf2ugmJEg+rionO0IkwbKX3Dh3T/oZjbA==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.973.8': @@ -215,15 +238,15 @@ packages: resolution: {integrity: sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-locate-window@3.965.4': - resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==} + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} engines: {node: '>=20.0.0'} '@aws-sdk/util-user-agent-browser@3.972.10': resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} - '@aws-sdk/util-user-agent-node@3.973.20': - resolution: {integrity: sha512-owEqyKr0z5hWwk+uHwudwNhyFMZ9f9eSWr/k/XD6yeDCI7hHyc56s4UOY1iBQmoramTbdAY4UCuLLEuKmjVXrg==} + '@aws-sdk/util-user-agent-node@3.973.24': + resolution: {integrity: sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==} engines: {node: '>=20.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -231,12 +254,16 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.972.18': - resolution: {integrity: sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA==} + '@aws-sdk/xml-builder@3.972.22': + resolution: {integrity: sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==} engines: {node: '>=20.0.0'} - '@aws/lambda-invoke-store@0.2.3': - resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==} + '@aws/bedrock-token-generator@1.1.0': + resolution: {integrity: sha512-i+DkWnfdA4j4sffy9dI4k3OGoOWqN8CTGdtO4IZ3c0kpKYFr6KyqzqLQmoRNrF3ACFcWj6u+J6cbBQ97j9wx5w==} + engines: {node: '>=16.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} engines: {node: '>=18.0.0'} '@babel/code-frame@7.29.0': @@ -247,230 +274,232 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.4.10': - resolution: {integrity: sha512-xxA3AphFQ1geij4JTHXv4EeSTda1IFn22ye9LdyVPoJU19fNVl0uzfEuhsfQ4Yue/0FaLs2/ccVi4UDiE7R30w==} + '@biomejs/biome@2.4.13': + resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.4.10': - resolution: {integrity: sha512-vuzzI1cWqDVzOMIkYyHbKqp+AkQq4K7k+UCXWpkYcY/HDn1UxdsbsfgtVpa40shem8Kax4TLDLlx8kMAecgqiw==} + '@biomejs/cli-darwin-arm64@2.4.13': + resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.4.10': - resolution: {integrity: sha512-14fzASRo+BPotwp7nWULy2W5xeUyFnTaq1V13Etrrxkrih+ez/2QfgFm5Ehtf5vSjtgx/IJycMMpn5kPd5ZNaA==} + '@biomejs/cli-darwin-x64@2.4.13': + resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.4.10': - resolution: {integrity: sha512-WrJY6UuiSD/Dh+nwK2qOTu8kdMDlLV3dLMmychIghHPAysWFq1/DGC1pVZx8POE3ZkzKR3PUUnVrtZfMfaJjyQ==} + '@biomejs/cli-linux-arm64-musl@2.4.13': + resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.4.10': - resolution: {integrity: sha512-7MH1CMW5uuxQ/s7FLST63qF8B3Hgu2HRdZ7tA1X1+mk+St4JOuIrqdhIBnnyqeyWJNI+Bww7Es5QZ0wIc1Cmkw==} + '@biomejs/cli-linux-arm64@2.4.13': + resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.4.10': - resolution: {integrity: sha512-kDTi3pI6PBN6CiczsWYOyP2zk0IJI08EWEQyDMQWW221rPaaEz6FvjLhnU07KMzLv8q3qSuoB93ua6inSQ55Tw==} + '@biomejs/cli-linux-x64-musl@2.4.13': + resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.4.10': - resolution: {integrity: sha512-tZLvEEi2u9Xu1zAqRjTcpIDGVtldigVvzug2fTuPG0ME/g8/mXpRPcNgLB22bGn6FvLJpHHnqLnwliOu8xjYrg==} + '@biomejs/cli-linux-x64@2.4.13': + resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.4.10': - resolution: {integrity: sha512-umwQU6qPzH+ISTf/eHyJ/QoQnJs3V9Vpjz2OjZXe9MVBZ7prgGafMy7yYeRGnlmDAn87AKTF3Q6weLoMGpeqdQ==} + '@biomejs/cli-win32-arm64@2.4.13': + resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.4.10': - resolution: {integrity: sha512-aW/JU5GuyH4uxMrNYpoC2kjaHlyJGLgIa3XkhPEZI0uKhZhJZU8BuEyJmvgzSPQNGozBwWjC972RaNdcJ9KyJg==} + '@biomejs/cli-win32-x64@2.4.13': + resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] - '@borewit/text-codec@0.2.1': - resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} '@canvas/image-data@1.1.0': resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==} - '@clack/core@1.2.0': - resolution: {integrity: sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==} + '@clack/core@1.3.0': + resolution: {integrity: sha512-xJPHpAmEQUBrXSLx0gF+q5K/IyihXpsHZcha+jB+tyahsKRK3Dxo4D0coZDewHo12NhiuzC3dTtMPbm53GEAAA==} + engines: {node: '>= 20.12.0'} - '@clack/prompts@1.2.0': - resolution: {integrity: sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==} + '@clack/prompts@1.3.0': + resolution: {integrity: sha512-GgcWwRCs/xPtaqlMy8qRhPnZf9vlWcWZNHAitnVQ3yk7JmSralSiq5q07yaffYE8SogtDm7zFeKccx1QNVARpw==} + engines: {node: '>= 20.12.0'} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -490,8 +519,8 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@google/genai@1.49.0': - resolution: {integrity: sha512-hO69Zl0H3x+L0KL4stl1pLYgnqnwHoLqtKy6MRlNnW8TAxjqMdOUVafomKd4z1BePkzoxJWbYILny9a2Zk43VQ==} + '@google/genai@1.51.0': + resolution: {integrity: sha512-vTZZF3CSimN7cn2zsLpW2p5WF0eZa5Gz69ITMPCNHpPrDlAstOfGifSfi0p/s9Z9400f7xJRkgvkQNrcM7pJ6w==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -499,258 +528,136 @@ packages: '@modelcontextprotocol/sdk': optional: true - '@homebridge/ciao@1.3.6': - resolution: {integrity: sha512-2F9N/15Q/GnoBXimr8PFg7fb1QrAQBvuZpaW2kseWOOy14Lzc3yZB1mT9N1Ju/4hlkboU33uHxtOxZkvkPoE/w==} + '@grammyjs/runner@2.0.3': + resolution: {integrity: sha512-nckmTs1dPWfVQteK9cxqxzE+0m1VRvluLWB8UgFzsjg62w3qthPJt0TYtJBEdG7OedvfQq4vnFAyE6iaMkR42A==} + engines: {node: '>=12.20.0 || >=14.13.1'} + peerDependencies: + grammy: ^1.13.1 + + '@grammyjs/transformer-throttler@1.2.1': + resolution: {integrity: sha512-CpWB0F3rJdUiKsq7826QhQsxbZi4wqfz1ccKX+fr+AOC+o8K7ZvS+wqX0suSu1QCsyUq2MDpNiKhyL2ZOJUS4w==} + engines: {node: ^12.20.0 || >=14.13.1} + peerDependencies: + grammy: ^1.0.0 + + '@grammyjs/types@3.26.0': + resolution: {integrity: sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==} + + '@homebridge/ciao@1.3.8': + resolution: {integrity: sha512-lNhpCsZVbdbjz2trFjQdzQ3cUIMZQMIMksi7wd3ntTIYgdaGLqT1Ms97DfVIJYHzRuduf56ISvgU8RRLTpK/ng==} hasBin: true - '@hono/node-server@1.19.13': - resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - '@img/sharp-darwin-x64@0.33.5': resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - '@img/sharp-win32-ia32@0.33.5': resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - '@img/sharp-win32-x64@0.33.5': resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -1015,30 +922,30 @@ packages: cpu: [x64] os: [win32] - '@mariozechner/clipboard@0.3.2': - resolution: {integrity: sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==} + '@mariozechner/clipboard@0.3.5': + resolution: {integrity: sha512-D3F+UrU9CR7roJt0zDLp6Oc+4/KlLDIrN4frH+6V90SJNW2KKUec1oCQIPaaDjCqeOsQyX9dyqYbImIQIM45PA==} engines: {node: '>= 10'} '@mariozechner/jiti@2.6.5': resolution: {integrity: sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==} hasBin: true - '@mariozechner/pi-agent-core@0.70.0': - resolution: {integrity: sha512-ZwfM5QPvSwza/apZhPIXjrI/blJBFqbVpK30ma4zNwH8VAyseKlzGDExCx/k+81Xydg60sQuG2BQVkYGmofuSg==} + '@mariozechner/pi-agent-core@0.73.0': + resolution: {integrity: sha512-ugcpvq0X9fr9fTSK29/3S4+KU/eeVMrBb7ZU3HqiF3xD7I1GlgumLj4FYmDrYSEA6+rzgNWlJUKwjKh9o0Z6AA==} engines: {node: '>=20.0.0'} - '@mariozechner/pi-ai@0.70.0': - resolution: {integrity: sha512-lVT9bb0eFkNr5YXvZ5r00TNA5r110fOO8uJV9VLCQ5GdtunWIjcptWitzIjjl2MF0/NDs7Kb2EwZctXQWWP7eA==} + '@mariozechner/pi-ai@0.73.0': + resolution: {integrity: sha512-phKOpcde/ssz6UYszkmaGJ9LF9mgt/AP8LrtSwsfap+kMSeFfSQ2/mCSBT1mLJ2BqVuff9uXs1/+op1aQeaafQ==} engines: {node: '>=20.0.0'} hasBin: true - '@mariozechner/pi-coding-agent@0.70.0': - resolution: {integrity: sha512-Sw5odG9BYIcRItb/o4Gmq0nSIgoWfx61Isjk3Gk4KqocxHZAOwZZYQ4mgb4GCsevqOMmAzX/H6PC52/TiN76fw==} + '@mariozechner/pi-coding-agent@0.73.0': + resolution: {integrity: sha512-Fs2dRIgtjDT8X5VDGNGzxj251B0FvkRsgX03YJv1FK4wg5Maj+jkf8/5A6tbPnPcXsCgs41xxJRf3tF5vJRccA==} engines: {node: '>=20.6.0'} hasBin: true - '@mariozechner/pi-tui@0.70.0': - resolution: {integrity: sha512-x/CwIMP8v9KNrmgEFA0+AWIwSWeNAitEI4eVQtQ6q2a0PpE+vx1+j2oc+iDPe7E1YqrMHXaNlHJVCaVAv/UYrg==} + '@mariozechner/pi-tui@0.73.0': + resolution: {integrity: sha512-St1W+tMPKHatfK+lblsKfL+SsFyFVMK2tW6xHpBfCiMuevbOCRo/CMatso7mu1642UO04ncmfCrrpUK5L9aoog==} engines: {node: '>=20.0.0'} '@mdx-js/mdx@3.1.1': @@ -1050,19 +957,19 @@ packages: '@types/react': '>=16' react: '>=16' - '@mintlify/cli@4.0.1090': - resolution: {integrity: sha512-SDA+KrZcMBoatfXujr+JeDEJkJ4kMpFKeeWPnC/vFJ4ojrGXfD0hWdSIdJ5MjLKfs2QtLqbvQErp7yBrj5r/QA==} + '@mintlify/cli@4.0.1146': + resolution: {integrity: sha512-G6nSUeHx3Q1lS7mwhWOUY3YOupPEgyo4UkUbc9Acg1cS9b8IhlQWX2PQnIek50TXVIY1I6qsOen+RLLdKCwhUQ==} engines: {node: '>=18.0.0'} hasBin: true '@mintlify/common@1.0.661': resolution: {integrity: sha512-/Hdiblzaomp+AWStQ4smhVMgesQhffzQjC9aYBnmLReNdh2Js+ccQFUaWL3TNIxwiS2esaZvsHSV/D+zyRS3hg==} - '@mintlify/common@1.0.835': - resolution: {integrity: sha512-JP73YjY85KmFyvh2xEkAzjoFVYXo6QdoXF1RtdDEZdOGph3hw06MxukiuvRDaXJbfpXVyLBWCkUuaUWVbcyqkA==} + '@mintlify/common@1.0.876': + resolution: {integrity: sha512-E9NN+0lyudwJ+m2oPXT+ZwX1M73m5B9zBpqFj81cZng6MVezMYo+yvNCtLVS6HV/THHos3vJKshNEXqoS9Cuzg==} - '@mintlify/link-rot@3.0.1010': - resolution: {integrity: sha512-XtIuDv/KTmRrMUNxlxke77+k163FLgm0I+e/U8PO1mlrBpnp2yiOi/mMvH4cfQpxFjjSJ6Apl5urH2nsircHKA==} + '@mintlify/link-rot@3.0.1054': + resolution: {integrity: sha512-xo1FKxQAcfzoDCHXnq153AG1EqUQDfIDhYD+h9olkhP4ZGQyzlLx/wdH4KxQiO9jW88NNAXpEavcN+udtaJBaA==} engines: {node: '>=18.0.0'} '@mintlify/mdx@3.0.4': @@ -1076,19 +983,19 @@ packages: resolution: {integrity: sha512-LIUkfA7l7ypHAAuOW74ZJws/NwNRqlDRD/U466jarXvvSlGhJec/6J4/I+IEcBvWDnc9anLFKmnGO04jPKgAsg==} engines: {node: '>=18.0.0'} - '@mintlify/models@0.0.287': - resolution: {integrity: sha512-3i1iateUUd21zVDEXIB0BUN0vcgD9WsfaXO/jB264LQCWXVI9nJEbfEXPeXVHUHwjKAExoRre5YXtb8BpWsmlA==} + '@mintlify/models@0.0.303': + resolution: {integrity: sha512-O4Y3x4Yxui6ki4oGREwqkQ2kCC44mHE0FTYCjDvsgkwqVWRO5H35q7xaLIPyoU+Fckgn2QZ2l4YhO/wxvohnlA==} engines: {node: '>=18.0.0'} '@mintlify/openapi-parser@0.0.8': resolution: {integrity: sha512-9MBRq9lS4l4HITYCrqCL7T61MOb20q9IdU7HWhqYMNMM1jGO1nHjXasFy61yZ8V6gMZyyKQARGVoZ0ZrYN48Og==} engines: {node: '>=18'} - '@mintlify/prebuild@1.0.977': - resolution: {integrity: sha512-/lKXOZHIPe9/Nrm5ZSUiEk8tK0RecJUf6WoY6haSxnNWrCvgVGBP2N9/Q3q3akT3ZK0ISZNVfM1z9K5L3dffKg==} + '@mintlify/prebuild@1.0.1019': + resolution: {integrity: sha512-XoabyWMyJtndqgj7uljPE+7cfw6mlxOk5kZoypGahcad2iDe5KdSInvM4G0kPev+ebHA1llnd0wmdAMDy/nm4Q==} - '@mintlify/previewing@4.0.1038': - resolution: {integrity: sha512-TzxrVZN96dpCSYeJ9JDklawL+schE0/Rdef3WmNSoMpRkutvnjhbR2biUpEyn9/X2wV7IWN0V9BGMemP9Tvhxg==} + '@mintlify/previewing@4.0.1080': + resolution: {integrity: sha512-0W+swZVGQn/J8UoPAWwZ8UUja5iIfPtvaUS0X3Lyv7g5sjAryCpeprWRKDFM9+M42sIs4ndbSQwatKvOUI+0Iw==} engines: {node: '>=18.0.0'} '@mintlify/scraping@4.0.522': @@ -1096,16 +1003,16 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@mintlify/scraping@4.0.699': - resolution: {integrity: sha512-vcn+W57lvMK82jEZ2s6v1uGXLZC2VBlpmbZ0cyJbZFbCTytgWFTJiB7a+9B+wMFto5zk2IRiwHeWxWagqiFVfQ==} + '@mintlify/scraping@4.0.740': + resolution: {integrity: sha512-FJXQTxer4+Hp2yU1j0D0beVspW8nTTZdu98ciejXvSM5T+pVG6kJ0GviKwQ0kj8XTRjDbaznnj6zm2RDf56oHA==} engines: {node: '>=18.0.0'} hasBin: true '@mintlify/validation@0.1.555': resolution: {integrity: sha512-11QVUReL4N5u8wSCgZt4RN7PA0jYQoMEBZ5IrUp5pgb5ZJBOoGV/vPsQrxPPa1cxsUDAuToNhtGxRQtOav/w8w==} - '@mintlify/validation@0.1.653': - resolution: {integrity: sha512-TK9dntGkxRQlnaFmsUSt7znd1QjGlS0nvEZymZfJICYxCaQbEkENEtKeex0gU8ot3XVJtImQhORelvzyjtEfEQ==} + '@mintlify/validation@0.1.686': + resolution: {integrity: sha512-XmI6HB8CuCxgyCA9m8geu9a6BQO6DfDtDXteiqj55Qz+S+cwZ1TW9YDI6hGzHbVQPpzuwyUU3I8l3TxaLfYPCw==} '@mistralai/mistralai@2.2.1': resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} @@ -1124,76 +1031,79 @@ packages: resolution: {integrity: sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==} engines: {node: '>=14.0.0'} - '@napi-rs/canvas-android-arm64@0.1.97': - resolution: {integrity: sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==} + '@napi-rs/canvas-android-arm64@0.1.100': + resolution: {integrity: sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.97': - resolution: {integrity: sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==} + '@napi-rs/canvas-darwin-arm64@0.1.100': + resolution: {integrity: sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.97': - resolution: {integrity: sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==} + '@napi-rs/canvas-darwin-x64@0.1.100': + resolution: {integrity: sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.97': - resolution: {integrity: sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==} + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': + resolution: {integrity: sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.97': - resolution: {integrity: sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==} + '@napi-rs/canvas-linux-arm64-gnu@0.1.100': + resolution: {integrity: sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-arm64-musl@0.1.97': - resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==} + '@napi-rs/canvas-linux-arm64-musl@0.1.100': + resolution: {integrity: sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': - resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==} + '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': + resolution: {integrity: sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@napi-rs/canvas-linux-x64-gnu@0.1.97': - resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==} + '@napi-rs/canvas-linux-x64-gnu@0.1.100': + resolution: {integrity: sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-linux-x64-musl@0.1.97': - resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==} + '@napi-rs/canvas-linux-x64-musl@0.1.100': + resolution: {integrity: sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-win32-arm64-msvc@0.1.97': - resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==} + '@napi-rs/canvas-win32-arm64-msvc@0.1.100': + resolution: {integrity: sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/canvas-win32-x64-msvc@0.1.97': - resolution: {integrity: sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==} + '@napi-rs/canvas-win32-x64-msvc@0.1.100': + resolution: {integrity: sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.97': - resolution: {integrity: sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==} + '@napi-rs/canvas@0.1.100': + resolution: {integrity: sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==} engines: {node: '>= 10'} + '@nodable/entities@2.1.0': + resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1218,8 +1128,8 @@ packages: '@protobufjs/base64@1.1.2': resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} '@protobufjs/eventemitter@1.1.0': resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} @@ -1230,8 +1140,8 @@ packages: '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + '@protobufjs/inquire@1.1.1': + resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==} '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} @@ -1239,8 +1149,8 @@ packages: '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} '@puppeteer/browsers@2.3.0': resolution: {integrity: sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==} @@ -1465,128 +1375,128 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} cpu: [x64] os: [win32] @@ -1637,12 +1547,38 @@ packages: resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} engines: {node: '>=12'} + '@slack/bolt@4.7.2': + resolution: {integrity: sha512-ALHtaS2iaP2WAWgX08yXsoCxEDitC6AqZs26ot6smXJQzBFMM4slVP+w3blLwzUV551xZ/+9RlBmWHsZDJJ5HA==} + engines: {node: '>=18', npm: '>=8.6.0'} + peerDependencies: + '@types/express': ^5.0.0 + + '@slack/logger@4.0.1': + resolution: {integrity: sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + + '@slack/oauth@3.0.5': + resolution: {integrity: sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==} + engines: {node: '>=18', npm: '>=8.6.0'} + + '@slack/socket-mode@2.0.7': + resolution: {integrity: sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + + '@slack/types@2.21.0': + resolution: {integrity: sha512-ZLMsKnD5KLRPmhFEoGoBQUD5Pc2bH3xFc5ygHlioEc0WmLGyZGoGCtMff4rpejrFnptrhfxcKpWxW4r3g39R0A==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + + '@slack/web-api@7.15.2': + resolution: {integrity: sha512-/m9qVFkiq85Oa/FSQwYIRDa/AO4qNYkDh4sRBK1WqEc2+RyG7w4tbU6rBIwUOcc/TmWOIr24Nraquxg7um5mYw==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + '@smithy/config-resolver@4.4.17': resolution: {integrity: sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==} engines: {node: '>=18.0.0'} - '@smithy/core@3.23.16': - resolution: {integrity: sha512-JStomOrINQA1VqNEopLsgcdgwd42au7mykKqVr30XFw89wLt9sDxJDi4djVPRwQmmzyTGy/uOvTc2ultMpFi1w==} + '@smithy/core@3.23.17': + resolution: {integrity: sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==} engines: {node: '>=18.0.0'} '@smithy/credential-provider-imds@4.2.14': @@ -1693,16 +1629,16 @@ packages: resolution: {integrity: sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.4.31': - resolution: {integrity: sha512-KJPdCIN2kOE2aGmqZd7eUTr4WQwOGgtLWgUkswGJggs7rBcQYQjcZMEDa3C0DwbOiXS9L8/wDoQHkfxBYLfiLw==} + '@smithy/middleware-endpoint@4.4.32': + resolution: {integrity: sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.5.4': - resolution: {integrity: sha512-/z7nIFK+ZRW3Ie/l3NEVGdy34LvmEOzBrtBAvgWZ/4PrKX0xP3kWm8pkfcwUk523SqxZhdbQP9JSXgjF77Uhpw==} + '@smithy/middleware-retry@4.5.7': + resolution: {integrity: sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.2.19': - resolution: {integrity: sha512-Q6y+W9h3iYVMCKWDoVge+OC1LKFqbEKaq8SIWG2X2bWJRpd/6dDLyICcNLT6PbjH3Rr6bmg/SeDB25XFOFfeEw==} + '@smithy/middleware-serde@4.2.20': + resolution: {integrity: sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==} engines: {node: '>=18.0.0'} '@smithy/middleware-stack@4.2.14': @@ -1713,8 +1649,8 @@ packages: resolution: {integrity: sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.6.0': - resolution: {integrity: sha512-P734cAoTFtuGfWa/R3jgBnGlURt2w9bYEBwQNMKf58sRM9RShirB2mKwLsVP+jlG/wxpCu8abv8NxdUts8tdLA==} + '@smithy/node-http-handler@4.6.1': + resolution: {integrity: sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==} engines: {node: '>=18.0.0'} '@smithy/property-provider@4.2.14': @@ -1733,8 +1669,8 @@ packages: resolution: {integrity: sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.3.0': - resolution: {integrity: sha512-9jKsBYQRPR0xBLgc2415RsA5PIcP2sis4oBdN9s0D13cg1B1284mNTjx9Yc+BEERXzuPm5ObktI96OxsKh8E9A==} + '@smithy/service-error-classification@4.3.1': + resolution: {integrity: sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==} engines: {node: '>=18.0.0'} '@smithy/shared-ini-file-loader@4.4.9': @@ -1745,8 +1681,8 @@ packages: resolution: {integrity: sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.12.12': - resolution: {integrity: sha512-daO7SJn4eM6ArbmrEs+/BTbH7af8AEbSL3OMQdcRvvn8tuUcR5rU2n6DgxIV53aXMS42uwK8NgKKCh5XgqYOPQ==} + '@smithy/smithy-client@4.12.13': + resolution: {integrity: sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==} engines: {node: '>=18.0.0'} '@smithy/types@4.14.1': @@ -1781,12 +1717,12 @@ packages: resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.3.48': - resolution: {integrity: sha512-hxVRVPYaRDWa6YQdse1aWX1qrksmLsvNyGBKdc32q4jFzSjxYVNWfstknAfR228TnzS4tzgswXRuYIbhXBuXFQ==} + '@smithy/util-defaults-mode-browser@4.3.49': + resolution: {integrity: sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.2.53': - resolution: {integrity: sha512-ybgCk+9JdBq8pYC8Y6U5fjyS8e4sboyAShetxPNL0rRBtaVl56GSFAxsolVBIea1tXR4LPIzL8i6xqmcf0+DCQ==} + '@smithy/util-defaults-mode-node@4.2.54': + resolution: {integrity: sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==} engines: {node: '>=18.0.0'} '@smithy/util-endpoints@3.4.2': @@ -1801,12 +1737,13 @@ packages: resolution: {integrity: sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.3.3': - resolution: {integrity: sha512-idjUvd4M9Jj6rXkhqw4H4reHoweuK4ZxYWyOrEp4N2rOF5VtaOlQGLDQJva/8WanNXk9ScQtsAb7o5UHGvFm4A==} + '@smithy/util-retry@4.3.6': + resolution: {integrity: sha512-p6/FO1n2KxMeQyna067i0uJ6TSbb165ZhnRtCpWh4Foxqbfc6oW+XITaL8QkFJj3KFnDe2URt4gOhgU06EP9ew==} engines: {node: '>=18.0.0'} + deprecated: '@smithy/util-retry v4.3.6 contains a bug in Adaptive Retry, see https://github.com/smithy-lang/smithy-typescript/issues/1993. Upgrade to 4.3.7+' - '@smithy/util-stream@4.5.24': - resolution: {integrity: sha512-na5vv2mBSDzXewLEEoWGI7LQQkfpmFEomBsmOpzLFjqGctm0iMwXY5lAwesY9pIaErkccW0qzEOUcYP+WKneXg==} + '@smithy/util-stream@4.5.25': + resolution: {integrity: sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==} engines: {node: '>=18.0.0'} '@smithy/util-uri-escape@4.2.2': @@ -1857,16 +1794,16 @@ packages: resolution: {integrity: sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==} engines: {node: '>=8'} - '@stoplight/spectral-core@1.21.0': - resolution: {integrity: sha512-oj4e/FrDLUhBRocIW+lRMKlJ/q/rDZw61HkLbTFsdMd+f/FTkli2xHNB1YC6n1mrMKjjvy7XlUuFkC7XxtgbWw==} + '@stoplight/spectral-core@1.22.0': + resolution: {integrity: sha512-4hTxMDs4TFUG4/jKjaZttA65gNuV2PCKI9+51I+J4nL6ylo17DlbW+sl6byKnBuV/85HxaV33ri5fEGlp8lTSA==} engines: {node: ^16.20 || ^18.18 || >= 20.17} '@stoplight/spectral-formats@1.8.2': resolution: {integrity: sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==} engines: {node: ^16.20 || ^18.18 || >= 20.17} - '@stoplight/spectral-functions@1.10.1': - resolution: {integrity: sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==} + '@stoplight/spectral-functions@1.10.2': + resolution: {integrity: sha512-PIfPUgTRo8EtAnL1MIrzhHoUuojSaE8shGSMaHS3BxGyc8d079BE5+TqJa1/WLUb9YT9JQnZ0Aj4xfi8NcJOIw==} engines: {node: ^16.20 || ^18.18 || >= 20.17} '@stoplight/spectral-parsers@1.0.5': @@ -1877,8 +1814,8 @@ packages: resolution: {integrity: sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==} engines: {node: ^16.20 || ^18.18 || >= 20.17} - '@stoplight/spectral-runtime@1.1.4': - resolution: {integrity: sha512-YHbhX3dqW0do6DhiPSgSGQzr6yQLlWybhKwWx0cqxjMwxej3TqLv3BXMfIUYFKKUqIwH4Q2mV8rrMM8qD2N0rQ==} + '@stoplight/spectral-runtime@1.1.5': + resolution: {integrity: sha512-6/HSCQBKnI4M5qonCKos2W7oggXv+U/ml+m/cAd4eJAYfIVEmaLUo03qSWIIl4cBc5ujJPmn2WnCiRrz1++P7Q==} engines: {node: ^16.20 || ^18.18 || >= 20.17} '@stoplight/types@13.20.0': @@ -1904,6 +1841,9 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} + '@telegraf/types@7.1.0': + resolution: {integrity: sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==} + '@tokenizer/inflate@0.4.1': resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} engines: {node: '>=18'} @@ -1917,11 +1857,14 @@ packages: '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/cookie@0.4.1': - resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} @@ -1941,15 +1884,27 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/katex@0.16.8': resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} @@ -1968,21 +1923,30 @@ packages: '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} - '@types/node@25.5.2': - resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} - '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2006,16 +1970,11 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vincentkoc/qrcode-tui@0.2.1': - resolution: {integrity: sha512-F2XVHMfasJ0q8G93gtcyU9Px0wMH6o6nIZLrZYSHc6dm9Pq3oCbHuVYYG/UQvJD0rhrGH3P9B6qgpCAqSDUw5w==} - engines: {node: '>=20'} - hasBin: true + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} - '@vitest/expect@4.1.2': - resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} - - '@vitest/mocker@4.1.2': - resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2025,20 +1984,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.2': - resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} - '@vitest/runner@4.1.2': - resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} - '@vitest/snapshot@4.1.2': - resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} - '@vitest/spy@4.1.2': - resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - '@vitest/utils@4.1.2': - resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -2116,8 +2075,8 @@ packages: ajv: optional: true - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} @@ -2166,15 +2125,9 @@ packages: arkregex@0.0.3: resolution: {integrity: sha512-bU21QJOJEFJK+BPNgv+5bVXkvRxyAvgnon75D92newgHxkBJTgiFwQxusyViYyJkETsddPlHyspshDQcCzmkNg==} - arkregex@0.0.5: - resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} - arktype@2.1.27: resolution: {integrity: sha512-enctOHxI4SULBv/TDtCVi5M8oLd4J5SVlPUblXDzSsOYQNMzmVbUosGBnJuZDKmFlN5Ie0/QVEuTE+Z5X1UhsQ==} - arktype@2.2.0: - resolution: {integrity: sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -2189,6 +2142,9 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2223,11 +2179,11 @@ packages: axios@1.10.0: resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} - axios@1.13.2: - resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} - b4a@1.8.0: - resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -2252,8 +2208,8 @@ packages: bare-abort-controller: optional: true - bare-fs@4.5.6: - resolution: {integrity: sha512-1QovqDrR80Pmt5HPAsMsXTCFcDYr+NSUKW6nd6WO5v0JBmnItc/irNRzm2KOQ5oZ69P37y+AMujNyNtG+1Rggw==} + bare-fs@4.7.1: + resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -2261,15 +2217,15 @@ packages: bare-buffer: optional: true - bare-os@3.8.6: - resolution: {integrity: sha512-l8xaNWWb/bXuzgsrlF5jaa5QYDJ9S0ddd54cP6CH+081+5iPrbJiCfBWQqrWYzmUhCbsH+WR6qxo9MeHVCr0MQ==} + bare-os@3.9.1: + resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} engines: {bare: '>=1.14.0'} bare-path@3.0.0: resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.12.0: - resolution: {integrity: sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==} + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} peerDependencies: bare-abort-controller: '*' bare-buffer: '*' @@ -2282,8 +2238,8 @@ packages: bare-events: optional: true - bare-url@2.4.0: - resolution: {integrity: sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==} + bare-url@2.4.2: + resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2292,8 +2248,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - basic-ftp@5.2.0: - resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==} + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} engines: {node: '>=10.0.0'} better-opn@3.0.2: @@ -2310,8 +2266,11 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + bn.js@4.12.3: + resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} + + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} body-parser@2.2.2: @@ -2321,26 +2280,38 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2373,8 +2344,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -2563,8 +2534,8 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} content-type@1.0.5: @@ -2578,21 +2549,13 @@ packages: resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} - engines: {node: '>= 0.6'} - - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -2781,8 +2744,8 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} dijkstrajs@1.0.3: @@ -2812,6 +2775,10 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dotenv@17.4.2: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} @@ -2832,10 +2799,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -2847,8 +2810,8 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.5.5: - resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==} + engine.io@6.6.7: + resolution: {integrity: sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ==} engines: {node: '>=10.2.0'} entities@4.5.0: @@ -2874,8 +2837,8 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-aggregate-error@1.0.14: @@ -2890,8 +2853,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -2905,8 +2868,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es-toolkit@1.45.1: - resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -2914,8 +2877,8 @@ packages: esast-util-from-js@2.0.1: resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true @@ -2930,6 +2893,10 @@ packages: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -2981,11 +2948,17 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} engines: {node: '>=18.0.0'} eventsource@3.0.7: @@ -3000,14 +2973,18 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - express-rate-limit@8.3.1: - resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==} + expr-eval-fork@3.0.3: + resolution: {integrity: sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg==} + engines: {node: '>=16.9.0'} + + express-rate-limit@8.4.1: + resolution: {integrity: sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' - express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + express@4.22.0: + resolution: {integrity: sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==} engines: {node: '>= 0.10.0'} express@5.2.1: @@ -3035,23 +3012,23 @@ packages: fast-memoize@2.5.2: resolution: {integrity: sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==} - fast-string-truncated-width@1.2.1: - resolution: {integrity: sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} - fast-string-width@1.1.0: - resolution: {integrity: sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==} + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-wrap-ansi@0.1.6: - resolution: {integrity: sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==} + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} - fast-xml-builder@1.1.4: - resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + fast-xml-builder@1.1.5: + resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==} - fast-xml-parser@5.5.8: - resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} + fast-xml-parser@5.7.2: + resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} hasBin: true fastq@1.20.1: @@ -3092,8 +3069,8 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} finalhandler@2.1.1: @@ -3107,8 +3084,8 @@ packages: fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -3263,6 +3240,10 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + global-agent@4.1.3: + resolution: {integrity: sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==} + engines: {node: '>=10.0'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -3298,6 +3279,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + grammy@1.42.0: + resolution: {integrity: sha512-1AdCge+AkjSdp2FwfICSFnVbl8Mq3KVHJDy+DgTI9+D6keJ0zWALPRKas5jv/8psiCzL4N2cEOcGW7O45Kn39g==} + engines: {node: ^12.20.0 || >=14.13.1} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} @@ -3325,8 +3310,8 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} hast-util-embedded@3.0.0: @@ -3396,8 +3381,8 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - hono@4.12.12: - resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} + hono@4.12.16: + resolution: {integrity: sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==} engines: {node: '>=16.9.0'} hosted-git-info@9.0.2: @@ -3416,10 +3401,6 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -3436,6 +3417,10 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} + http_ece@1.2.0: + resolution: {integrity: sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==} + engines: {node: '>=16'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -3519,6 +3504,10 @@ packages: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + ip-regex@4.3.0: resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} engines: {node: '>=8'} @@ -3527,8 +3516,8 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.3.0: - resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} is-alphabetical@2.0.1: @@ -3587,6 +3576,9 @@ packages: engines: {node: '>=8'} hasBin: true + is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3715,8 +3707,8 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.2.2: - resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} @@ -3764,8 +3756,8 @@ packages: jsonc-parser@2.2.1: resolution: {integrity: sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} jsonpath-plus@10.4.0: resolution: {integrity: sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==} @@ -3776,6 +3768,10 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} @@ -3785,8 +3781,8 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - katex@0.16.44: - resolution: {integrity: sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==} + katex@0.16.45: + resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} hasBin: true keytar@7.9.0: @@ -3795,8 +3791,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - koffi@2.15.1: - resolution: {integrity: sha512-mnc0C0crx/xMSljb5s9QbnLrlFHprioFO1hkXyuSuO/QtbpLDa0l/uM21944UfQunMKmp3/r789DTDxVyyH6aA==} + koffi@2.16.1: + resolution: {integrity: sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==} lcm@0.0.3: resolution: {integrity: sha512-TB+ZjoillV6B26Vspf9l2L/vKaRY/4ep3hahcyVkCGFgsTNRUQdc24bQeNFiZeoxH0vr5+7SfNRMQuPHv/1IrQ==} @@ -3843,14 +3839,35 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.topath@4.5.2: resolution: {integrity: sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -3866,8 +3883,8 @@ packages: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lru-cache@11.2.6: - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + lru-cache@11.3.5: + resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} engines: {node: 20 || >=22} lru-cache@7.18.3: @@ -3893,6 +3910,10 @@ packages: engines: {node: '>= 18'} hasBin: true + matcher@4.0.0: + resolution: {integrity: sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -3971,8 +3992,8 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} @@ -4134,12 +4155,15 @@ packages: resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -4175,8 +4199,12 @@ packages: engines: {node: '>=10'} hasBin: true - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -4191,8 +4219,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -4211,12 +4239,12 @@ packages: resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} engines: {node: '>= 10'} - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} engines: {node: '>= 0.4.0'} - next-mdx-remote-client@1.1.6: - resolution: {integrity: sha512-O4HIpi44d6SismhfG5W78aTUfgxfbsj6FgoM4/G3o4Vtcobt0Ej439IiDPkv+IqsmtouVYG1tGAsz1DIuj9Tfg==} + next-mdx-remote-client@1.1.7: + resolution: {integrity: sha512-12Ap5Z/tFIETMXFSBTH2IFEhJAso7MvOJ5ICyesA4q6FM4vtAcmb+4ZKa4tV1IVQJLBVqOhaEfIESZzdwjmrQQ==} engines: {node: '>=20.9.0'} peerDependencies: react: '>= 18.3.0 < 19.0.0' @@ -4236,11 +4264,19 @@ packages: node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} + node-addon-api@8.7.0: + resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} + engines: {node: ^18 || ^20 || >= 21} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead + node-edge-tts@1.2.10: + resolution: {integrity: sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ==} + hasBin: true + node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -4263,8 +4299,9 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-readable-to-web-readable-stream@0.4.2: - resolution: {integrity: sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true non-error@0.1.0: resolution: {integrity: sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ==} @@ -4281,8 +4318,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - oauth4webapi@3.8.5: - resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + oauth4webapi@3.8.6: + resolution: {integrity: sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -4318,11 +4355,11 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - oniguruma-parser@0.12.1: - resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} - oniguruma-to-es@4.3.5: - resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} @@ -4340,8 +4377,8 @@ packages: zod: optional: true - openai@6.34.0: - resolution: {integrity: sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw==} + openai@6.36.0: + resolution: {integrity: sha512-Has2YbIusMq9wQEierFsgf9c783dy1y9arX459LmphNacEkkM5yxi2RIyXP0LmkOroQyW19iTwALHL8Yf26UKA==} hasBin: true peerDependencies: ws: ^8.18.0 @@ -4355,23 +4392,18 @@ packages: openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - openclaw@2026.4.23: - resolution: {integrity: sha512-Er5q6z4bBFgO2T+YwJcL7WqhH8dQfB0X4h2sEO2K2C1HiEGQ3Wt9qjeNWRoGDSkGsSxAY59vtOxU8JPzZ0SgjQ==} + openclaw@2026.5.4: + resolution: {integrity: sha512-nbLukSwhBr/wqFLKwLKMDCXJ0lIQYpKKJ4Zzp6ZoN6erLjRUkU5MyU5wbY5oChl6yM7TinBYZ3lRw9V6K07DSQ==} engines: {node: '>=22.14.0'} hasBin: true - peerDependencies: - '@napi-rs/canvas': ^0.1.89 - node-llama-cpp: 3.18.1 - peerDependenciesMeta: - node-llama-cpp: - optional: true - openid-client@6.8.2: - resolution: {integrity: sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==} + openid-client@6.8.4: + resolution: {integrity: sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==} - osc-progress@0.3.0: - resolution: {integrity: sha512-4/8JfsetakdeEa4vAYV45FW20aY+B/+K8NEXp5Eiar3wR8726whgHrbSg5Ar/ZY1FLJ/AGtUqV7W2IVF+Gvp9A==} - engines: {node: '>=20'} + openshell@0.1.0: + resolution: {integrity: sha512-B7jLewH+d73hraWcrSFgNOjvd+frW5JPejkTpqgj2EJBjX/Yk1Y4blgP5pDl4FwrBxfmwsTKR08Uwgrdo+xpSg==} + engines: {node: '>=18'} + hasBin: true own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} @@ -4385,6 +4417,10 @@ packages: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4393,6 +4429,10 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + p-retry@4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} @@ -4401,6 +4441,14 @@ packages: resolution: {integrity: sha512-CJbQCKdfSX3fIh8/QKgS+9rjm7OBNUTmwWswAFQAhc8j1NR1dsEDETUEuVUtQHZpV+J03LqWBEwvu0g1Yn+TYg==} engines: {node: '>=12.20'} + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-timeout@4.1.0: + resolution: {integrity: sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==} + engines: {node: '>=10'} + p-timeout@5.1.0: resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} engines: {node: '>=12'} @@ -4471,8 +4519,8 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-expression-matcher@1.2.0: - resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} engines: {node: '>=14.0.0'} path-key@3.1.1: @@ -4486,18 +4534,18 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pdfjs-dist@5.6.205: - resolution: {integrity: sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==} - engines: {node: '>=20.19.0 || >=22.13.0 || >=24'} + pdfjs-dist@5.7.284: + resolution: {integrity: sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==} + engines: {node: '>=22.13.0 || >=24'} pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -4509,8 +4557,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pify@2.3.0: @@ -4528,6 +4576,11 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -4595,6 +4648,10 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.13: + resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -4625,8 +4682,8 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - protobufjs@7.5.4: - resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + protobufjs@7.5.6: + resolution: {integrity: sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==} engines: {node: '>=12.0.0'} proxy-addr@2.0.7: @@ -4674,12 +4731,12 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -4696,8 +4753,8 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} raw-body@3.0.2: @@ -4827,11 +4884,14 @@ packages: remark-gfm@4.0.0: resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + remark-math@6.0.0: resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} - remark-mdx-remove-esm@1.2.3: - resolution: {integrity: sha512-n6r36SaE+7cno7pmshWbGzYolDVLxJm5EKuw67+q4SPQT6kelNJHyZAiFYYtOB0axh+/1xF4BC57Ec3jncAGXQ==} + remark-mdx-remove-esm@1.3.1: + resolution: {integrity: sha512-POa8abdiuicD2e+zQkclxzJa5JEGLtV8XIOFVvisnGuw4l4xd6dfQozedwqR8JTeXQmxLebvYhlbwHoQP9RWkw==} peerDependencies: unified: ^11 @@ -4841,12 +4901,18 @@ packages: remark-mdx@3.1.0: resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} remark-rehype@11.1.1: resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + remark-smartypants@3.0.2: resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} engines: {node: '>=16.0.0'} @@ -4879,8 +4945,8 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -4916,8 +4982,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.59.0: - resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4935,8 +5001,8 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -4945,6 +5011,9 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-compare@1.1.4: + resolution: {integrity: sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -4959,6 +5028,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sandwich-stream@2.0.2: + resolution: {integrity: sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==} + engines: {node: '>= 0.10'} + sax@1.6.0: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} @@ -4979,8 +5052,8 @@ packages: engines: {node: '>=10'} hasBin: true - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} send@1.2.1: @@ -4991,8 +5064,12 @@ packages: resolution: {integrity: sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA==} engines: {node: '>=20'} - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + serialize-error@8.1.0: + resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} + engines: {node: '>=10'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} serve-static@2.2.1: @@ -5027,10 +5104,6 @@ packages: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -5042,8 +5115,8 @@ packages: shiki@3.23.0: resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -5071,10 +5144,6 @@ packages: simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - simple-eval@1.0.1: - resolution: {integrity: sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==} - engines: {node: '>=12'} - simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -5103,8 +5172,8 @@ packages: resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} engines: {node: '>=10.0.0'} - socket.io@4.7.2: - resolution: {integrity: sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==} + socket.io@4.8.0: + resolution: {integrity: sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==} engines: {node: '>=10.2.0'} socks-proxy-agent@10.0.0: @@ -5115,8 +5184,8 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + socks@2.8.8: + resolution: {integrity: sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map-js@1.2.1: @@ -5175,10 +5244,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -5186,8 +5251,8 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - std-env@4.0.0: - resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} @@ -5219,6 +5284,9 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -5234,8 +5302,8 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strnum@2.2.0: - resolution: {integrity: sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==} + strnum@2.2.3: + resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} strtok3@10.3.5: resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} @@ -5284,8 +5352,8 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar-stream@3.1.8: - resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==} + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} tar@6.1.15: resolution: {integrity: sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==} @@ -5299,6 +5367,11 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + telegraf@4.16.3: + resolution: {integrity: sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==} + engines: {node: ^12.20.0 || >=14.13.1} + hasBin: true + text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} @@ -5318,12 +5391,12 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.4: - resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} tinyrainbow@3.1.0: @@ -5345,6 +5418,11 @@ packages: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} + tokenjuice@0.7.0: + resolution: {integrity: sha512-RZIyFmzztf/8V4q1cUS5L+q8UISMSfsjzh4UoWVxQbE7/zX91SfNmHpNqopqyB4oc5hwH4XqC9O/yakVzJCu8g==} + engines: {node: '>=20'} + hasBin: true + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5352,6 +5430,14 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + tree-sitter-bash@0.25.1: + resolution: {integrity: sha512-7hMytuYIMoXOq24yRulgIxthE9YmggZIOHCyPTTuJcu6EU54tYD+4G39cUb28kxC6jMf/AbPfWGLQtgPTdh3xw==} + peerDependencies: + tree-sitter: ^0.25.0 + peerDependenciesMeta: + tree-sitter: + optional: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -5377,6 +5463,10 @@ packages: resolution: {integrity: sha512-XuELoRpMR+sq8fuWwX7P0bcj+PRNiicOKDEb3fGNURhxWVyykCi9BNq7c4uVz7h7P0sj8qgBsr5SWS6yBClq3g==} engines: {node: '>=16'} + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + tsup@8.5.1: resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} engines: {node: '>=18'} @@ -5399,13 +5489,17 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - twoslash-protocol@0.3.6: - resolution: {integrity: sha512-FHGsJ9Q+EsNr5bEbgG3hnbkvEBdW5STgPU824AHUjB4kw0Dn4p8tABT7Ncg1Ie6V0+mDg3Qpy41VafZXcQhWMA==} + twoslash-protocol@0.3.8: + resolution: {integrity: sha512-HmvAHoiEviK8LqvAQyc9/irkdvwTUiR1fHmNwH/0gq8EHxyBt4PWVPixjEXg6wJu1u6yBrILEWXGK9Kw58/8yQ==} - twoslash@0.3.6: - resolution: {integrity: sha512-VuI5OKl+MaUO9UIW3rXKoPgHI3X40ZgB/j12VY6h98Ae1mCBihjPvhOPeJWlxCYcmSbmeZt5ZKkK0dsVtp+6pA==} + twoslash@0.3.8: + resolution: {integrity: sha512-OeDz0kDl8sqPUN3nr7gqcvOs70f5lZsdhKYTX3/SgB9OvdadzzoYJI/4SBXhXV1HG8E9fLc+e17itoRYTxmoig==} peerDependencies: - typescript: ^5.5.0 + typescript: ^5.5.0 || ^6.0.0 + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} @@ -5415,8 +5509,8 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - type-fest@5.5.0: - resolution: {integrity: sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==} + type-fest@5.6.0: + resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} type-is@1.6.18: @@ -5427,8 +5521,8 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typebox@1.1.28: - resolution: {integrity: sha512-OqHTHRfBpQHWPipoeFVkNgxKjjXhGY49UgekFrOuaC9O59/Hws8KHjGa1AUfNYnxWSfuNRkTB81FAL/QbTWFrg==} + typebox@1.1.37: + resolution: {integrity: sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -5446,16 +5540,16 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} uhyphen@0.2.0: resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} @@ -5471,18 +5565,15 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} - undici@7.24.6: - resolution: {integrity: sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} - undici@8.1.0: - resolution: {integrity: sha512-E9MkTS4xXLnRPYqxH2e6Hr2/49e7WFDKczKcCaFH4VaZs2iNvHMqeIkyUAD9vM8kujy9TjVrRlQ5KkdEJxB2pw==} + undici@8.2.0: + resolution: {integrity: sha512-Z+4Hx9GE26Lh9Upwfnc8C7SsrpBPGaM/Gm6kMFtiG7c+5IvQKlXi/t+9x9DrrCh29cww5TSP9YdVaBcnLDs5fQ==} engines: {node: '>=22.19.0'} unified@11.0.5: @@ -5539,9 +5630,6 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -5597,6 +5685,7 @@ packages: uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true vary@1.1.2: @@ -5655,18 +5744,20 @@ packages: yaml: optional: true - vitest@4.1.2: - resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.2 - '@vitest/browser-preview': 4.1.2 - '@vitest/browser-webdriverio': 4.1.2 - '@vitest/ui': 4.1.2 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -5683,6 +5774,10 @@ packages: optional: true '@vitest/browser-webdriverio': optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true '@vitest/ui': optional: true happy-dom: @@ -5693,10 +5788,18 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-push@3.6.7: + resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==} + engines: {node: '>= 16'} + hasBin: true + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-tree-sitter@0.26.8: + resolution: {integrity: sha512-4sUwi7ZyOrIk5KLgYLkc2A/F0LFMQnBhfb+2Cdl7ik4ePJ6JD+fk4ofI2sA5eGawBKBaK4Vntt7Ww5KcEsay4A==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5751,18 +5854,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -5819,6 +5910,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.8.4: + resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -5866,10 +5962,10 @@ packages: peerDependencies: zod: ^3.20.0 - zod-to-json-schema@3.25.1: - resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: - zod: ^3.25 || ^4 + zod: ^3.25.28 || ^4 zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} @@ -5880,17 +5976,17 @@ packages: zod@3.24.0: resolution: {integrity: sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==} - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: - '@agentclientprotocol/sdk@0.19.0(zod@4.3.6)': + '@agentclientprotocol/sdk@0.21.0(zod@4.4.3)': dependencies: - zod: 4.3.6 + zod: 4.4.3 '@alcalzone/ansi-tokenize@0.2.5': dependencies: @@ -5899,15 +5995,21 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@anthropic-ai/sdk@0.90.0(zod@4.3.6)': + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.4.3 + + '@anthropic-ai/sdk@0.93.0(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: - zod: 4.3.6 + zod: 4.4.3 - '@anthropic-ai/vertex-sdk@0.16.0(zod@4.3.6)': + '@anthropic-ai/vertex-sdk@0.16.0(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.90.0(zod@4.3.6) + '@anthropic-ai/sdk': 0.93.0(zod@4.4.3) google-auth-library: 9.15.1 transitivePeerDependencies: - encoding @@ -5918,14 +6020,8 @@ snapshots: dependencies: '@ark/util': 0.55.0 - '@ark/schema@0.56.0': - dependencies: - '@ark/util': 0.56.0 - '@ark/util@0.55.0': {} - '@ark/util@0.56.0': {} - '@asyncapi/parser@3.4.0': dependencies: '@asyncapi/specs': 6.8.1 @@ -5933,16 +6029,16 @@ snapshots: '@stoplight/json': 3.21.0 '@stoplight/json-ref-readers': 1.2.2 '@stoplight/json-ref-resolver': 3.1.6 - '@stoplight/spectral-core': 1.21.0 - '@stoplight/spectral-functions': 1.10.1 + '@stoplight/spectral-core': 1.22.0 + '@stoplight/spectral-functions': 1.10.2 '@stoplight/spectral-parsers': 1.0.5 '@stoplight/spectral-ref-resolver': 1.0.5 '@stoplight/types': 13.20.0 '@types/json-schema': 7.0.15 '@types/urijs': 1.19.26 - ajv: 8.18.0 - ajv-errors: 3.0.0(ajv@8.18.0) - ajv-formats: 2.1.1(ajv@8.18.0) + ajv: 8.20.0 + ajv-errors: 3.0.0(ajv@8.20.0) + ajv-formats: 2.1.1(ajv@8.20.0) avsc: 5.7.9 js-yaml: 4.1.0 jsonpath-plus: 10.4.0 @@ -5966,7 +6062,7 @@ snapshots: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-locate-window': 3.965.4 + '@aws-sdk/util-locate-window': 3.965.5 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -5986,27 +6082,27 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-bedrock-runtime@3.1035.0': + '@aws-sdk/client-bedrock-runtime@3.1042.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.4 - '@aws-sdk/credential-provider-node': 3.972.35 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-node': 3.972.39 '@aws-sdk/eventstream-handler-node': 3.972.14 '@aws-sdk/middleware-eventstream': 3.972.10 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.34 + '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/middleware-websocket': 3.972.16 '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/token-providers': 3.1035.0 + '@aws-sdk/token-providers': 3.1042.0 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-endpoints': 3.996.8 '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.20 + '@aws-sdk/util-user-agent-node': 3.973.24 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.23.16 + '@smithy/core': 3.23.17 '@smithy/eventstream-serde-browser': 4.2.14 '@smithy/eventstream-serde-config-resolver': 4.3.14 '@smithy/eventstream-serde-node': 4.2.14 @@ -6014,78 +6110,177 @@ snapshots: '@smithy/hash-node': 4.2.14 '@smithy/invalid-dependency': 4.2.14 '@smithy/middleware-content-length': 4.2.14 - '@smithy/middleware-endpoint': 4.4.31 - '@smithy/middleware-retry': 4.5.4 - '@smithy/middleware-serde': 4.2.19 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-stream': 4.5.25 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-bedrock@3.1042.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-node': 3.972.39 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/token-providers': 3.1042.0 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.24 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-cognito-identity@3.1042.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-node': 3.972.39 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-user-agent': 3.972.38 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.24 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 '@smithy/middleware-stack': 4.2.14 '@smithy/node-config-provider': 4.3.14 - '@smithy/node-http-handler': 4.6.0 + '@smithy/node-http-handler': 4.6.1 '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 '@smithy/url-parser': 4.2.14 '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.48 - '@smithy/util-defaults-mode-node': 4.2.53 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 '@smithy/util-endpoints': 3.4.2 '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.3 - '@smithy/util-stream': 4.5.24 + '@smithy/util-retry': 4.3.6 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.974.4': + '@aws-sdk/core@3.974.8': dependencies: '@aws-sdk/types': 3.973.8 - '@aws-sdk/xml-builder': 3.972.18 - '@smithy/core': 3.23.16 + '@aws-sdk/xml-builder': 3.972.22 + '@smithy/core': 3.23.17 '@smithy/node-config-provider': 4.3.14 '@smithy/property-provider': 4.2.14 '@smithy/protocol-http': 5.3.14 '@smithy/signature-v4': 5.3.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 '@smithy/util-base64': 4.3.2 '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.3 + '@smithy/util-retry': 4.3.6 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.30': + '@aws-sdk/credential-provider-cognito-identity@3.972.31': dependencies: - '@aws-sdk/core': 3.974.4 + '@aws-sdk/nested-clients': 3.997.6 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/types': 4.14.1 tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt - '@aws-sdk/credential-provider-http@3.972.32': + '@aws-sdk/credential-provider-env@3.972.34': dependencies: - '@aws-sdk/core': 3.974.4 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.8 '@aws-sdk/types': 3.973.8 '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.6.0 + '@smithy/node-http-handler': 4.6.1 '@smithy/property-provider': 4.2.14 '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 - '@smithy/util-stream': 4.5.24 + '@smithy/util-stream': 4.5.25 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.972.34': + '@aws-sdk/credential-provider-ini@3.972.38': dependencies: - '@aws-sdk/core': 3.974.4 - '@aws-sdk/credential-provider-env': 3.972.30 - '@aws-sdk/credential-provider-http': 3.972.32 - '@aws-sdk/credential-provider-login': 3.972.34 - '@aws-sdk/credential-provider-process': 3.972.30 - '@aws-sdk/credential-provider-sso': 3.972.34 - '@aws-sdk/credential-provider-web-identity': 3.972.34 - '@aws-sdk/nested-clients': 3.997.2 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-login': 3.972.38 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 + '@aws-sdk/nested-clients': 3.997.6 '@aws-sdk/types': 3.973.8 '@smithy/credential-provider-imds': 4.2.14 '@smithy/property-provider': 4.2.14 @@ -6095,10 +6290,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-login@3.972.34': + '@aws-sdk/credential-provider-login@3.972.38': dependencies: - '@aws-sdk/core': 3.974.4 - '@aws-sdk/nested-clients': 3.997.2 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/protocol-http': 5.3.14 @@ -6108,14 +6303,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.972.35': + '@aws-sdk/credential-provider-node@3.972.39': dependencies: - '@aws-sdk/credential-provider-env': 3.972.30 - '@aws-sdk/credential-provider-http': 3.972.32 - '@aws-sdk/credential-provider-ini': 3.972.34 - '@aws-sdk/credential-provider-process': 3.972.30 - '@aws-sdk/credential-provider-sso': 3.972.34 - '@aws-sdk/credential-provider-web-identity': 3.972.34 + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-ini': 3.972.38 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 '@aws-sdk/types': 3.973.8 '@smithy/credential-provider-imds': 4.2.14 '@smithy/property-provider': 4.2.14 @@ -6125,20 +6320,20 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.972.30': + '@aws-sdk/credential-provider-process@3.972.34': dependencies: - '@aws-sdk/core': 3.974.4 + '@aws-sdk/core': 3.974.8 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.34': + '@aws-sdk/credential-provider-sso@3.972.38': dependencies: - '@aws-sdk/core': 3.974.4 - '@aws-sdk/nested-clients': 3.997.2 - '@aws-sdk/token-providers': 3.1035.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/token-providers': 3.1041.0 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -6147,10 +6342,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.972.34': + '@aws-sdk/credential-provider-web-identity@3.972.38': dependencies: - '@aws-sdk/core': 3.974.4 - '@aws-sdk/nested-clients': 3.997.2 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -6159,6 +6354,31 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-providers@3.1042.0': + dependencies: + '@aws-sdk/client-cognito-identity': 3.1042.0 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/credential-provider-cognito-identity': 3.972.31 + '@aws-sdk/credential-provider-env': 3.972.34 + '@aws-sdk/credential-provider-http': 3.972.36 + '@aws-sdk/credential-provider-ini': 3.972.38 + '@aws-sdk/credential-provider-login': 3.972.38 + '@aws-sdk/credential-provider-node': 3.972.39 + '@aws-sdk/credential-provider-process': 3.972.34 + '@aws-sdk/credential-provider-sso': 3.972.38 + '@aws-sdk/credential-provider-web-identity': 3.972.38 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/credential-provider-imds': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/property-provider': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/eventstream-handler-node@3.972.14': dependencies: '@aws-sdk/types': 3.973.8 @@ -6189,37 +6409,37 @@ snapshots: '@aws-sdk/middleware-recursion-detection@3.972.11': dependencies: '@aws-sdk/types': 3.973.8 - '@aws/lambda-invoke-store': 0.2.3 + '@aws/lambda-invoke-store': 0.2.4 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.33': + '@aws-sdk/middleware-sdk-s3@3.972.37': dependencies: - '@aws-sdk/core': 3.974.4 + '@aws-sdk/core': 3.974.8 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-arn-parser': 3.972.3 - '@smithy/core': 3.23.16 + '@smithy/core': 3.23.17 '@smithy/node-config-provider': 4.3.14 '@smithy/protocol-http': 5.3.14 '@smithy/signature-v4': 5.3.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 '@smithy/util-config-provider': 4.2.2 '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.24 + '@smithy/util-stream': 4.5.25 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.972.34': + '@aws-sdk/middleware-user-agent@3.972.38': dependencies: - '@aws-sdk/core': 3.974.4 + '@aws-sdk/core': 3.974.8 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-endpoints': 3.996.8 - '@smithy/core': 3.23.16 + '@smithy/core': 3.23.17 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.14.1 - '@smithy/util-retry': 4.3.3 + '@smithy/util-retry': 4.3.6 tslib: 2.8.1 '@aws-sdk/middleware-websocket@3.972.16': @@ -6237,45 +6457,45 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.2': + '@aws-sdk/nested-clients@3.997.6': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.4 + '@aws-sdk/core': 3.974.8 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.34 + '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/signature-v4-multi-region': 3.996.21 + '@aws-sdk/signature-v4-multi-region': 3.996.25 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-endpoints': 3.996.8 '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.20 + '@aws-sdk/util-user-agent-node': 3.973.24 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.23.16 + '@smithy/core': 3.23.17 '@smithy/fetch-http-handler': 5.3.17 '@smithy/hash-node': 4.2.14 '@smithy/invalid-dependency': 4.2.14 '@smithy/middleware-content-length': 4.2.14 - '@smithy/middleware-endpoint': 4.4.31 - '@smithy/middleware-retry': 4.5.4 - '@smithy/middleware-serde': 4.2.19 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 '@smithy/middleware-stack': 4.2.14 '@smithy/node-config-provider': 4.3.14 - '@smithy/node-http-handler': 4.6.0 + '@smithy/node-http-handler': 4.6.1 '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 '@smithy/url-parser': 4.2.14 '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.48 - '@smithy/util-defaults-mode-node': 4.2.53 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 '@smithy/util-endpoints': 3.4.2 '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.3 + '@smithy/util-retry': 4.3.6 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 transitivePeerDependencies: @@ -6289,19 +6509,31 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.21': + '@aws-sdk/signature-v4-multi-region@3.996.25': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.33 + '@aws-sdk/middleware-sdk-s3': 3.972.37 '@aws-sdk/types': 3.973.8 '@smithy/protocol-http': 5.3.14 '@smithy/signature-v4': 5.3.14 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1035.0': + '@aws-sdk/token-providers@3.1041.0': + dependencies: + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/token-providers@3.1042.0': dependencies: - '@aws-sdk/core': 3.974.4 - '@aws-sdk/nested-clients': 3.997.2 + '@aws-sdk/core': 3.974.8 + '@aws-sdk/nested-clients': 3.997.6 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -6334,7 +6566,7 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.965.4': + '@aws-sdk/util-locate-window@3.965.5': dependencies: tslib: 2.8.1 @@ -6345,22 +6577,37 @@ snapshots: bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.973.20': + '@aws-sdk/util-user-agent-node@3.973.24': dependencies: - '@aws-sdk/middleware-user-agent': 3.972.34 + '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/types': 3.973.8 '@smithy/node-config-provider': 4.3.14 '@smithy/types': 4.14.1 '@smithy/util-config-provider': 4.2.2 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.18': + '@aws-sdk/xml-builder@3.972.22': dependencies: + '@nodable/entities': 2.1.0 '@smithy/types': 4.14.1 - fast-xml-parser: 5.5.8 + fast-xml-parser: 5.7.2 tslib: 2.8.1 - '@aws/lambda-invoke-store@0.2.3': {} + '@aws/bedrock-token-generator@1.1.0': + dependencies: + '@aws-sdk/credential-providers': 3.1042.0 + '@aws-sdk/util-format-url': 3.972.10 + '@smithy/config-resolver': 4.4.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/signature-v4': 5.3.14 + '@smithy/types': 4.14.1 + transitivePeerDependencies: + - aws-crt + + '@aws/lambda-invoke-store@0.2.4': {} '@babel/code-frame@7.29.0': dependencies: @@ -6370,140 +6617,140 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} - '@babel/runtime@7.28.6': {} + '@babel/runtime@7.29.2': {} - '@biomejs/biome@2.4.10': + '@biomejs/biome@2.4.13': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.10 - '@biomejs/cli-darwin-x64': 2.4.10 - '@biomejs/cli-linux-arm64': 2.4.10 - '@biomejs/cli-linux-arm64-musl': 2.4.10 - '@biomejs/cli-linux-x64': 2.4.10 - '@biomejs/cli-linux-x64-musl': 2.4.10 - '@biomejs/cli-win32-arm64': 2.4.10 - '@biomejs/cli-win32-x64': 2.4.10 + '@biomejs/cli-darwin-arm64': 2.4.13 + '@biomejs/cli-darwin-x64': 2.4.13 + '@biomejs/cli-linux-arm64': 2.4.13 + '@biomejs/cli-linux-arm64-musl': 2.4.13 + '@biomejs/cli-linux-x64': 2.4.13 + '@biomejs/cli-linux-x64-musl': 2.4.13 + '@biomejs/cli-win32-arm64': 2.4.13 + '@biomejs/cli-win32-x64': 2.4.13 - '@biomejs/cli-darwin-arm64@2.4.10': + '@biomejs/cli-darwin-arm64@2.4.13': optional: true - '@biomejs/cli-darwin-x64@2.4.10': + '@biomejs/cli-darwin-x64@2.4.13': optional: true - '@biomejs/cli-linux-arm64-musl@2.4.10': + '@biomejs/cli-linux-arm64-musl@2.4.13': optional: true - '@biomejs/cli-linux-arm64@2.4.10': + '@biomejs/cli-linux-arm64@2.4.13': optional: true - '@biomejs/cli-linux-x64-musl@2.4.10': + '@biomejs/cli-linux-x64-musl@2.4.13': optional: true - '@biomejs/cli-linux-x64@2.4.10': + '@biomejs/cli-linux-x64@2.4.13': optional: true - '@biomejs/cli-win32-arm64@2.4.10': + '@biomejs/cli-win32-arm64@2.4.13': optional: true - '@biomejs/cli-win32-x64@2.4.10': + '@biomejs/cli-win32-x64@2.4.13': optional: true - '@borewit/text-codec@0.2.1': {} + '@borewit/text-codec@0.2.2': {} '@canvas/image-data@1.1.0': {} - '@clack/core@1.2.0': + '@clack/core@1.3.0': dependencies: - fast-wrap-ansi: 0.1.6 + fast-wrap-ansi: 0.2.0 sisteransi: 1.0.5 - '@clack/prompts@1.2.0': + '@clack/prompts@1.3.0': dependencies: - '@clack/core': 1.2.0 - fast-string-width: 1.1.0 - fast-wrap-ansi: 0.1.6 + '@clack/core': 1.3.0 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.0 sisteransi: 1.0.5 - '@emnapi/runtime@1.8.1': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.3': + '@esbuild/aix-ppc64@0.27.7': optional: true - '@esbuild/android-arm64@0.27.3': + '@esbuild/android-arm64@0.27.7': optional: true - '@esbuild/android-arm@0.27.3': + '@esbuild/android-arm@0.27.7': optional: true - '@esbuild/android-x64@0.27.3': + '@esbuild/android-x64@0.27.7': optional: true - '@esbuild/darwin-arm64@0.27.3': + '@esbuild/darwin-arm64@0.27.7': optional: true - '@esbuild/darwin-x64@0.27.3': + '@esbuild/darwin-x64@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.27.3': + '@esbuild/freebsd-arm64@0.27.7': optional: true - '@esbuild/freebsd-x64@0.27.3': + '@esbuild/freebsd-x64@0.27.7': optional: true - '@esbuild/linux-arm64@0.27.3': + '@esbuild/linux-arm64@0.27.7': optional: true - '@esbuild/linux-arm@0.27.3': + '@esbuild/linux-arm@0.27.7': optional: true - '@esbuild/linux-ia32@0.27.3': + '@esbuild/linux-ia32@0.27.7': optional: true - '@esbuild/linux-loong64@0.27.3': + '@esbuild/linux-loong64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.27.3': + '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/linux-ppc64@0.27.3': + '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/linux-riscv64@0.27.3': + '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/linux-s390x@0.27.3': + '@esbuild/linux-s390x@0.27.7': optional: true - '@esbuild/linux-x64@0.27.3': + '@esbuild/linux-x64@0.27.7': optional: true - '@esbuild/netbsd-arm64@0.27.3': + '@esbuild/netbsd-arm64@0.27.7': optional: true - '@esbuild/netbsd-x64@0.27.3': + '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/openbsd-arm64@0.27.3': + '@esbuild/openbsd-arm64@0.27.7': optional: true - '@esbuild/openbsd-x64@0.27.3': + '@esbuild/openbsd-x64@0.27.7': optional: true - '@esbuild/openharmony-arm64@0.27.3': + '@esbuild/openharmony-arm64@0.27.7': optional: true - '@esbuild/sunos-x64@0.27.3': + '@esbuild/sunos-x64@0.27.7': optional: true - '@esbuild/win32-arm64@0.27.3': + '@esbuild/win32-arm64@0.27.7': optional: true - '@esbuild/win32-ia32@0.27.3': + '@esbuild/win32-ia32@0.27.7': optional: true - '@esbuild/win32-x64@0.27.3': + '@esbuild/win32-x64@0.27.7': optional: true '@floating-ui/core@1.7.5': @@ -6523,20 +6770,32 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@google/genai@1.49.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))': + '@google/genai@1.51.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))': dependencies: google-auth-library: 10.6.2 p-retry: 4.6.2 - protobufjs: 7.5.4 + protobufjs: 7.5.6 ws: 8.20.0 optionalDependencies: - '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.6) + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@homebridge/ciao@1.3.6': + '@grammyjs/runner@2.0.3(grammy@1.42.0)': + dependencies: + abort-controller: 3.0.0 + grammy: 1.42.0 + + '@grammyjs/transformer-throttler@1.2.1(grammy@1.42.0)': + dependencies: + bottleneck: 2.19.5 + grammy: 1.42.0 + + '@grammyjs/types@3.26.0': {} + + '@homebridge/ciao@1.3.8': dependencies: debug: 4.4.3 fast-deep-equal: 3.1.3 @@ -6545,305 +6804,209 @@ snapshots: transitivePeerDependencies: - supports-color - '@hono/node-server@1.19.13(hono@4.12.12)': + '@hono/node-server@1.19.14(hono@4.12.16)': dependencies: - hono: 4.12.12 - - '@img/colour@1.1.0': {} + hono: 4.12.16 '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - '@img/sharp-darwin-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - '@img/sharp-libvips-linux-arm@1.0.5': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - '@img/sharp-libvips-linux-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - '@img/sharp-linux-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true - '@img/sharp-linux-arm@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.5 optional: true - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true - - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true - - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true - '@img/sharp-linux-s390x@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true - '@img/sharp-linux-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.4 optional: true - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - '@img/sharp-wasm32@0.33.5': dependencies: - '@emnapi/runtime': 1.8.1 - optional: true - - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.8.1 - optional: true - - '@img/sharp-win32-arm64@0.34.5': + '@emnapi/runtime': 1.10.0 optional: true '@img/sharp-win32-ia32@0.33.5': optional: true - '@img/sharp-win32-ia32@0.34.5': - optional: true - '@img/sharp-win32-x64@0.33.5': optional: true - '@img/sharp-win32-x64@0.34.5': - optional: true - '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.5.2)': + '@inquirer/checkbox@4.3.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/confirm@5.1.21(@types/node@25.5.2)': + '@inquirer/confirm@5.1.21(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/core@10.3.2(@types/node@25.5.2)': + '@inquirer/core@10.3.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/editor@4.2.23(@types/node@25.5.2)': + '@inquirer/editor@4.2.23(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/external-editor': 1.0.3(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/external-editor': 1.0.3(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/expand@4.0.23(@types/node@25.5.2)': + '@inquirer/expand@4.0.23(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/external-editor@1.0.3(@types/node@25.5.2)': + '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.5.2)': + '@inquirer/input@4.3.1(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/number@3.0.23(@types/node@25.5.2)': + '@inquirer/number@3.0.23(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/password@4.0.23(@types/node@25.5.2)': + '@inquirer/password@4.0.23(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 - - '@inquirer/prompts@7.9.0(@types/node@25.5.2)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.5.2) - '@inquirer/confirm': 5.1.21(@types/node@25.5.2) - '@inquirer/editor': 4.2.23(@types/node@25.5.2) - '@inquirer/expand': 4.0.23(@types/node@25.5.2) - '@inquirer/input': 4.3.1(@types/node@25.5.2) - '@inquirer/number': 3.0.23(@types/node@25.5.2) - '@inquirer/password': 4.0.23(@types/node@25.5.2) - '@inquirer/rawlist': 4.1.11(@types/node@25.5.2) - '@inquirer/search': 3.2.2(@types/node@25.5.2) - '@inquirer/select': 4.4.2(@types/node@25.5.2) + '@types/node': 25.6.0 + + '@inquirer/prompts@7.9.0(@types/node@25.6.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.6.0) + '@inquirer/confirm': 5.1.21(@types/node@25.6.0) + '@inquirer/editor': 4.2.23(@types/node@25.6.0) + '@inquirer/expand': 4.0.23(@types/node@25.6.0) + '@inquirer/input': 4.3.1(@types/node@25.6.0) + '@inquirer/number': 3.0.23(@types/node@25.6.0) + '@inquirer/password': 4.0.23(@types/node@25.6.0) + '@inquirer/rawlist': 4.1.11(@types/node@25.6.0) + '@inquirer/search': 3.2.2(@types/node@25.6.0) + '@inquirer/select': 4.4.2(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/rawlist@4.1.11(@types/node@25.5.2)': + '@inquirer/rawlist@4.1.11(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/search@3.2.2(@types/node@25.5.2)': + '@inquirer/search@3.2.2(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/select@4.4.2(@types/node@25.5.2)': + '@inquirer/select@4.4.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/type@3.0.10(@types/node@25.5.2)': + '@inquirer/type@3.0.10(@types/node@25.6.0)': optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@isaacs/fs-minipass@4.0.1': dependencies: @@ -6934,7 +7097,7 @@ snapshots: '@mariozechner/clipboard-win32-x64-msvc@0.3.2': optional: true - '@mariozechner/clipboard@0.3.2': + '@mariozechner/clipboard@0.3.5': optionalDependencies: '@mariozechner/clipboard-darwin-arm64': 0.3.2 '@mariozechner/clipboard-darwin-universal': 0.3.2 @@ -6953,10 +7116,10 @@ snapshots: std-env: 3.10.0 yoctocolors: 2.1.2 - '@mariozechner/pi-agent-core@0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': + '@mariozechner/pi-agent-core@0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3)': dependencies: - '@mariozechner/pi-ai': 0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - typebox: 1.1.28 + '@mariozechner/pi-ai': 0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + typebox: 1.1.37 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -6966,19 +7129,19 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': + '@mariozechner/pi-ai@0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.90.0(zod@4.3.6) - '@aws-sdk/client-bedrock-runtime': 3.1035.0 - '@google/genai': 1.49.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1042.0 + '@google/genai': 1.51.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)) '@mistralai/mistralai': 2.2.1 chalk: 5.6.2 - openai: 6.26.0(ws@8.20.0)(zod@4.3.6) + openai: 6.26.0(ws@8.20.0)(zod@4.4.3) partial-json: 0.1.7 proxy-agent: 6.5.0 - typebox: 1.1.28 - undici: 7.24.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) + typebox: 1.1.37 + undici: 7.25.0 + zod-to-json-schema: 3.25.2(zod@4.4.3) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -6988,31 +7151,31 @@ snapshots: - ws - zod - '@mariozechner/pi-coding-agent@0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': + '@mariozechner/pi-coding-agent@0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3)': dependencies: '@mariozechner/jiti': 2.6.5 - '@mariozechner/pi-agent-core': 0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-tui': 0.70.0 + '@mariozechner/pi-agent-core': 0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-ai': 0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-tui': 0.73.0 '@silvia-odwyer/photon-node': 0.3.4 chalk: 5.6.2 cli-highlight: 2.1.11 - diff: 8.0.3 + diff: 8.0.4 extract-zip: 2.0.1 file-type: 21.3.4 glob: 13.0.6 hosted-git-info: 9.0.2 ignore: 7.0.5 marked: 15.0.12 - minimatch: 10.2.4 + minimatch: 10.2.5 proper-lockfile: 4.1.2 strip-ansi: 7.2.0 - typebox: 1.1.28 - undici: 7.24.6 + typebox: 1.1.37 + undici: 7.25.0 uuid: 14.0.0 - yaml: 2.8.3 + yaml: 2.8.4 optionalDependencies: - '@mariozechner/clipboard': 0.3.2 + '@mariozechner/clipboard': 0.3.5 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - aws-crt @@ -7022,7 +7185,7 @@ snapshots: - ws - zod - '@mariozechner/pi-tui@0.70.0': + '@mariozechner/pi-tui@0.73.0': dependencies: '@types/mime-types': 2.1.4 chalk: 5.6.2 @@ -7030,7 +7193,7 @@ snapshots: marked: 15.0.12 mime-types: 3.0.2 optionalDependencies: - koffi: 2.15.1 + koffi: 2.16.1 '@mdx-js/mdx@3.1.1': dependencies: @@ -7050,14 +7213,14 @@ snapshots: recma-jsx: 1.0.1(acorn@8.16.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 - remark-mdx: 3.1.0 + remark-mdx: 3.1.1 remark-parse: 11.0.0 - remark-rehype: 11.1.1 + remark-rehype: 11.1.2 source-map: 0.7.6 unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color @@ -7068,14 +7231,15 @@ snapshots: '@types/react': 19.2.14 react: 19.2.3 - '@mintlify/cli@4.0.1090(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/node@25.5.2)(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/cli@4.0.1146(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/node@25.6.0)(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@6.0.3)(yaml@2.8.4)': dependencies: - '@inquirer/prompts': 7.9.0(@types/node@25.5.2) - '@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/link-rot': 3.0.1010(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/previewing': 4.0.1038(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@inquirer/prompts': 7.9.0(@types/node@25.6.0) + '@mintlify/common': 1.0.876(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/link-rot': 3.0.1054(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/models': 0.0.303 + '@mintlify/prebuild': 1.0.1019(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/previewing': 4.0.1080(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/validation': 0.1.686(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) adm-zip: 0.5.16 chalk: 5.2.0 color: 4.2.3 @@ -7083,17 +7247,17 @@ snapshots: front-matter: 4.0.2 fs-extra: 11.2.0 ink: 6.3.0(@types/react@19.2.14)(react@19.2.3) - inquirer: 12.3.0(@types/node@25.5.2) + inquirer: 12.3.0(@types/node@25.6.0) js-yaml: 4.1.0 mdast-util-mdx-jsx: 3.2.0 open: 8.4.2 - openid-client: 6.8.2 + openid-client: 6.8.4 posthog-node: 5.17.2 react: 19.2.3 semver: 7.7.2 unist-util-visit: 5.0.0 yargs: 17.7.1 - zod: 4.3.6 + zod: 4.4.3 optionalDependencies: keytar: 7.9.0 transitivePeerDependencies: @@ -7115,13 +7279,13 @@ snapshots: - utf-8-validate - yaml - '@mintlify/common@1.0.661(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@mintlify/common@1.0.661(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': dependencies: '@asyncapi/parser': 3.4.0 - '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) '@mintlify/models': 0.0.255 '@mintlify/openapi-parser': 0.0.8 - '@mintlify/validation': 0.1.555(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/validation': 0.1.555(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) '@sindresorhus/slugify': 2.2.0 '@types/mdast': 4.0.4 acorn: 8.11.2 @@ -7175,14 +7339,14 @@ snapshots: - ts-node - typescript - '@mintlify/common@1.0.835(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/common@1.0.876(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4)': dependencies: '@asyncapi/parser': 3.4.0 '@asyncapi/specs': 6.8.1 - '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@mintlify/models': 0.0.287 + '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/models': 0.0.303 '@mintlify/openapi-parser': 0.0.8 - '@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/validation': 0.1.686(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) '@sindresorhus/slugify': 2.2.0 '@types/mdast': 4.0.4 acorn: 8.11.2 @@ -7197,7 +7361,7 @@ snapshots: hex-rgb: 5.0.0 ignore: 7.0.5 js-yaml: 4.1.0 - lodash: 4.17.21 + lodash: 4.18.1 mdast-util-from-markdown: 2.0.2 mdast-util-gfm: 3.0.0 mdast-util-mdx: 3.0.0 @@ -7217,7 +7381,7 @@ snapshots: remark-rehype: 11.1.1 remark-stringify: 11.0.0 sucrase: 3.35.1 - tailwindcss: 3.4.19(yaml@2.8.3) + tailwindcss: 3.4.19(yaml@2.8.4) unified: 11.0.5 unist-builder: 4.0.0 unist-util-map: 4.0.0 @@ -7239,13 +7403,14 @@ snapshots: - typescript - yaml - '@mintlify/link-rot@3.0.1010(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/link-rot@3.0.1054(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4)': dependencies: - '@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/previewing': 4.0.1038(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/scraping': 4.0.522(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/common': 1.0.876(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/models': 0.0.303 + '@mintlify/prebuild': 1.0.1019(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/previewing': 4.0.1080(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/scraping': 4.0.522(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/validation': 0.1.686(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) fs-extra: 11.1.0 unist-util-visit: 4.1.2 transitivePeerDependencies: @@ -7267,27 +7432,27 @@ snapshots: - utf-8-validate - yaml - '@mintlify/mdx@3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@mintlify/mdx@3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': dependencies: '@radix-ui/react-popover': 1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3) '@shikijs/transformers': 3.23.0 - '@shikijs/twoslash': 3.23.0(typescript@5.9.3) - arktype: 2.2.0 + '@shikijs/twoslash': 3.23.0(typescript@6.0.3) + arktype: 2.1.27 hast-util-to-string: 3.0.1 mdast-util-from-markdown: 2.0.3 mdast-util-gfm: 3.1.0 mdast-util-mdx-jsx: 3.2.0 mdast-util-to-hast: 13.2.1 - next-mdx-remote-client: 1.1.6(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(unified@11.0.5) + next-mdx-remote-client: 1.1.7(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(unified@11.0.5) react: 19.2.3 react-dom: 18.3.1(react@19.2.3) rehype-katex: 7.0.1 - remark-gfm: 4.0.0 + remark-gfm: 4.0.1 remark-math: 6.0.0 remark-smartypants: 3.0.2 shiki: 3.23.0 unified: 11.0.5 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 transitivePeerDependencies: - '@types/react' - supports-color @@ -7300,28 +7465,28 @@ snapshots: transitivePeerDependencies: - debug - '@mintlify/models@0.0.287': + '@mintlify/models@0.0.303': dependencies: - axios: 1.13.2 + axios: 1.15.0 openapi-types: 12.1.3 transitivePeerDependencies: - debug '@mintlify/openapi-parser@0.0.8': dependencies: - ajv: 8.18.0 - ajv-draft-04: 1.0.0(ajv@8.18.0) - ajv-formats: 3.0.1(ajv@8.18.0) + ajv: 8.20.0 + ajv-draft-04: 1.0.0(ajv@8.20.0) + ajv-formats: 3.0.1(ajv@8.20.0) jsonpointer: 5.0.1 leven: 4.1.0 yaml: 2.8.3 - '@mintlify/prebuild@1.0.977(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/prebuild@1.0.1019(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4)': dependencies: - '@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) + '@mintlify/common': 1.0.876(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) '@mintlify/openapi-parser': 0.0.8 - '@mintlify/scraping': 4.0.699(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/scraping': 4.0.740(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/validation': 0.1.686(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) chalk: 5.3.0 favicons: 7.2.0 front-matter: 4.0.2 @@ -7349,16 +7514,16 @@ snapshots: - utf-8-validate - yaml - '@mintlify/previewing@4.0.1038(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/previewing@4.0.1080(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(typescript@6.0.3)(yaml@2.8.4)': dependencies: - '@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/prebuild': 1.0.977(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/validation': 0.1.653(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/common': 1.0.876(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/prebuild': 1.0.1019(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) + '@mintlify/validation': 0.1.686(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) adm-zip: 0.5.16 better-opn: 3.0.2 chalk: 5.2.0 chokidar: 3.5.3 - express: 4.18.2 + express: 4.22.0 front-matter: 4.0.2 fs-extra: 11.1.0 got: 13.0.0 @@ -7368,7 +7533,7 @@ snapshots: js-yaml: 4.1.0 openapi-types: 12.1.3 react: 19.2.3 - socket.io: 4.7.2 + socket.io: 4.8.0 tar: 6.1.15 unist-util-visit: 4.1.2 yargs: 17.7.1 @@ -7389,16 +7554,16 @@ snapshots: - utf-8-validate - yaml - '@mintlify/scraping@4.0.522(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@mintlify/scraping@4.0.522(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': dependencies: - '@mintlify/common': 1.0.661(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/common': 1.0.661(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) '@mintlify/openapi-parser': 0.0.8 fs-extra: 11.1.1 hast-util-to-mdast: 10.1.0 js-yaml: 4.1.0 mdast-util-mdx-jsx: 3.1.3 neotraverse: 0.6.18 - puppeteer: 22.14.0(typescript@5.9.3) + puppeteer: 22.14.0(typescript@6.0.3) rehype-parse: 9.0.1 remark-gfm: 4.0.0 remark-mdx: 3.0.1 @@ -7424,16 +7589,16 @@ snapshots: - typescript - utf-8-validate - '@mintlify/scraping@4.0.699(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/scraping@4.0.740(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4)': dependencies: - '@mintlify/common': 1.0.835(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)(yaml@2.8.3) + '@mintlify/common': 1.0.876(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)(yaml@2.8.4) '@mintlify/openapi-parser': 0.0.8 fs-extra: 11.1.1 hast-util-to-mdast: 10.1.0 js-yaml: 4.1.0 mdast-util-mdx-jsx: 3.1.3 neotraverse: 0.6.18 - puppeteer: 22.14.0(typescript@5.9.3) + puppeteer: 22.14.0(typescript@6.0.3) rehype-parse: 9.0.1 remark-gfm: 4.0.0 remark-mdx: 3.0.1 @@ -7460,9 +7625,9 @@ snapshots: - utf-8-validate - yaml - '@mintlify/validation@0.1.555(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@mintlify/validation@0.1.555(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': dependencies: - '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) '@mintlify/models': 0.0.255 arktype: 2.1.27 js-yaml: 4.1.0 @@ -7482,14 +7647,14 @@ snapshots: - supports-color - typescript - '@mintlify/validation@0.1.653(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@mintlify/validation@0.1.686(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': dependencies: - '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@mintlify/models': 0.0.287 + '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@mintlify/models': 0.0.303 arktype: 2.1.27 js-yaml: 4.1.0 lcm: 0.0.3 - lodash: 4.17.21 + lodash: 4.18.1 neotraverse: 0.6.18 object-hash: 3.0.0 openapi-types: 12.1.3 @@ -7508,82 +7673,85 @@ snapshots: '@mistralai/mistralai@2.2.1': dependencies: ws: 8.20.0 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) transitivePeerDependencies: - bufferutil - utf-8-validate - '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: - '@hono/node-server': 1.19.13(hono@4.12.12) - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) + '@hono/node-server': 1.19.14(hono@4.12.16) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 - eventsource-parser: 3.0.6 + eventsource-parser: 3.0.8 express: 5.2.1 - express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.12.12 - jose: 6.2.2 + express-rate-limit: 8.4.1(express@5.2.1) + hono: 4.12.16 + jose: 6.2.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) transitivePeerDependencies: - supports-color '@mozilla/readability@0.6.0': {} - '@napi-rs/canvas-android-arm64@0.1.97': + '@napi-rs/canvas-android-arm64@0.1.100': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.97': + '@napi-rs/canvas-darwin-arm64@0.1.100': optional: true - '@napi-rs/canvas-darwin-x64@0.1.97': + '@napi-rs/canvas-darwin-x64@0.1.100': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.97': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.97': + '@napi-rs/canvas-linux-arm64-gnu@0.1.100': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.97': + '@napi-rs/canvas-linux-arm64-musl@0.1.100': optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': + '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.97': + '@napi-rs/canvas-linux-x64-gnu@0.1.100': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.97': + '@napi-rs/canvas-linux-x64-musl@0.1.100': optional: true - '@napi-rs/canvas-win32-arm64-msvc@0.1.97': + '@napi-rs/canvas-win32-arm64-msvc@0.1.100': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.97': + '@napi-rs/canvas-win32-x64-msvc@0.1.100': optional: true - '@napi-rs/canvas@0.1.97': + '@napi-rs/canvas@0.1.100': optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.97 - '@napi-rs/canvas-darwin-arm64': 0.1.97 - '@napi-rs/canvas-darwin-x64': 0.1.97 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.97 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.97 - '@napi-rs/canvas-linux-arm64-musl': 0.1.97 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.97 - '@napi-rs/canvas-linux-x64-gnu': 0.1.97 - '@napi-rs/canvas-linux-x64-musl': 0.1.97 - '@napi-rs/canvas-win32-arm64-msvc': 0.1.97 - '@napi-rs/canvas-win32-x64-msvc': 0.1.97 + '@napi-rs/canvas-android-arm64': 0.1.100 + '@napi-rs/canvas-darwin-arm64': 0.1.100 + '@napi-rs/canvas-darwin-x64': 0.1.100 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.100 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.100 + '@napi-rs/canvas-linux-arm64-musl': 0.1.100 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.100 + '@napi-rs/canvas-linux-x64-gnu': 0.1.100 + '@napi-rs/canvas-linux-x64-musl': 0.1.100 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.100 + '@napi-rs/canvas-win32-x64-msvc': 0.1.100 + optional: true + + '@nodable/entities@2.1.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -7609,24 +7777,24 @@ snapshots: '@protobufjs/base64@1.1.2': {} - '@protobufjs/codegen@2.0.4': {} + '@protobufjs/codegen@2.0.5': {} '@protobufjs/eventemitter@1.1.0': {} '@protobufjs/fetch@1.1.0': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 + '@protobufjs/inquire': 1.1.1 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.0': {} + '@protobufjs/inquire@1.1.1': {} '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.0': {} + '@protobufjs/utf8@1.1.1': {} '@puppeteer/browsers@2.3.0': dependencies: @@ -7634,7 +7802,7 @@ snapshots: extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 - semver: 7.7.4 + semver: 7.7.2 tar-fs: 3.1.2 unbzip2-stream: 1.4.3 yargs: 17.7.2 @@ -7823,79 +7991,79 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rollup/rollup-android-arm-eabi@4.59.0': + '@rollup/rollup-android-arm-eabi@4.60.2': optional: true - '@rollup/rollup-android-arm64@4.59.0': + '@rollup/rollup-android-arm64@4.60.2': optional: true - '@rollup/rollup-darwin-arm64@4.59.0': + '@rollup/rollup-darwin-arm64@4.60.2': optional: true - '@rollup/rollup-darwin-x64@4.59.0': + '@rollup/rollup-darwin-x64@4.60.2': optional: true - '@rollup/rollup-freebsd-arm64@4.59.0': + '@rollup/rollup-freebsd-arm64@4.60.2': optional: true - '@rollup/rollup-freebsd-x64@4.59.0': + '@rollup/rollup-freebsd-x64@4.60.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.59.0': + '@rollup/rollup-linux-arm-musleabihf@4.60.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.59.0': + '@rollup/rollup-linux-arm64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.59.0': + '@rollup/rollup-linux-arm64-musl@4.60.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.59.0': + '@rollup/rollup-linux-loong64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-loong64-musl@4.59.0': + '@rollup/rollup-linux-loong64-musl@4.60.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.59.0': + '@rollup/rollup-linux-ppc64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-ppc64-musl@4.59.0': + '@rollup/rollup-linux-ppc64-musl@4.60.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.59.0': + '@rollup/rollup-linux-riscv64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.59.0': + '@rollup/rollup-linux-riscv64-musl@4.60.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.59.0': + '@rollup/rollup-linux-s390x-gnu@4.60.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.59.0': + '@rollup/rollup-linux-x64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-x64-musl@4.59.0': + '@rollup/rollup-linux-x64-musl@4.60.2': optional: true - '@rollup/rollup-openbsd-x64@4.59.0': + '@rollup/rollup-openbsd-x64@4.60.2': optional: true - '@rollup/rollup-openharmony-arm64@4.59.0': + '@rollup/rollup-openharmony-arm64@4.60.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.59.0': + '@rollup/rollup-win32-arm64-msvc@4.60.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.59.0': + '@rollup/rollup-win32-ia32-msvc@4.60.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.59.0': + '@rollup/rollup-win32-x64-gnu@4.60.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.59.0': + '@rollup/rollup-win32-x64-msvc@4.60.2': optional: true '@shikijs/core@3.23.0': @@ -7909,7 +8077,7 @@ snapshots: dependencies: '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.5 + oniguruma-to-es: 4.3.6 '@shikijs/engine-oniguruma@3.23.0': dependencies: @@ -7929,12 +8097,12 @@ snapshots: '@shikijs/core': 3.23.0 '@shikijs/types': 3.23.0 - '@shikijs/twoslash@3.23.0(typescript@5.9.3)': + '@shikijs/twoslash@3.23.0(typescript@6.0.3)': dependencies: '@shikijs/core': 3.23.0 '@shikijs/types': 3.23.0 - twoslash: 0.3.6(typescript@5.9.3) - typescript: 5.9.3 + twoslash: 0.3.8(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -7960,6 +8128,71 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 + '@slack/bolt@4.7.2(@types/express@5.0.6)': + dependencies: + '@slack/logger': 4.0.1 + '@slack/oauth': 3.0.5 + '@slack/socket-mode': 2.0.7 + '@slack/types': 2.21.0 + '@slack/web-api': 7.15.2 + '@types/express': 5.0.6 + axios: 1.15.0 + express: 5.2.1 + path-to-regexp: 8.4.2 + raw-body: 3.0.2 + tsscmp: 1.0.6 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@slack/logger@4.0.1': + dependencies: + '@types/node': 25.6.0 + + '@slack/oauth@3.0.5': + dependencies: + '@slack/logger': 4.0.1 + '@slack/web-api': 7.15.2 + '@types/jsonwebtoken': 9.0.10 + '@types/node': 25.6.0 + jsonwebtoken: 9.0.3 + transitivePeerDependencies: + - debug + + '@slack/socket-mode@2.0.7': + dependencies: + '@slack/logger': 4.0.1 + '@slack/web-api': 7.15.2 + '@types/node': 25.6.0 + '@types/ws': 8.18.1 + eventemitter3: 5.0.4 + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@slack/types@2.21.0': {} + + '@slack/web-api@7.15.2': + dependencies: + '@slack/logger': 4.0.1 + '@slack/types': 2.21.0 + '@types/node': 25.6.0 + '@types/retry': 0.12.0 + axios: 1.15.0 + eventemitter3: 5.0.4 + form-data: 4.0.5 + is-electron: 2.2.2 + is-stream: 2.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + retry: 0.13.1 + transitivePeerDependencies: + - debug + '@smithy/config-resolver@4.4.17': dependencies: '@smithy/node-config-provider': 4.3.14 @@ -7969,7 +8202,7 @@ snapshots: '@smithy/util-middleware': 4.2.14 tslib: 2.8.1 - '@smithy/core@3.23.16': + '@smithy/core@3.23.17': dependencies: '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.14.1 @@ -7977,7 +8210,7 @@ snapshots: '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.24 + '@smithy/util-stream': 4.5.25 '@smithy/util-utf8': 4.2.2 '@smithy/uuid': 1.1.2 tslib: 2.8.1 @@ -8054,10 +8287,10 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.4.31': + '@smithy/middleware-endpoint@4.4.32': dependencies: - '@smithy/core': 3.23.16 - '@smithy/middleware-serde': 4.2.19 + '@smithy/core': 3.23.17 + '@smithy/middleware-serde': 4.2.20 '@smithy/node-config-provider': 4.3.14 '@smithy/shared-ini-file-loader': 4.4.9 '@smithy/types': 4.14.1 @@ -8065,22 +8298,22 @@ snapshots: '@smithy/util-middleware': 4.2.14 tslib: 2.8.1 - '@smithy/middleware-retry@4.5.4': + '@smithy/middleware-retry@4.5.7': dependencies: - '@smithy/core': 3.23.16 + '@smithy/core': 3.23.17 '@smithy/node-config-provider': 4.3.14 '@smithy/protocol-http': 5.3.14 - '@smithy/service-error-classification': 4.3.0 - '@smithy/smithy-client': 4.12.12 + '@smithy/service-error-classification': 4.3.1 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.3 + '@smithy/util-retry': 4.3.6 '@smithy/uuid': 1.1.2 tslib: 2.8.1 - '@smithy/middleware-serde@4.2.19': + '@smithy/middleware-serde@4.2.20': dependencies: - '@smithy/core': 3.23.16 + '@smithy/core': 3.23.17 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.14.1 tslib: 2.8.1 @@ -8097,7 +8330,7 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.6.0': + '@smithy/node-http-handler@4.6.1': dependencies: '@smithy/protocol-http': 5.3.14 '@smithy/querystring-builder': 4.2.14 @@ -8125,7 +8358,7 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@smithy/service-error-classification@4.3.0': + '@smithy/service-error-classification@4.3.1': dependencies: '@smithy/types': 4.14.1 @@ -8145,14 +8378,14 @@ snapshots: '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 - '@smithy/smithy-client@4.12.12': + '@smithy/smithy-client@4.12.13': dependencies: - '@smithy/core': 3.23.16 - '@smithy/middleware-endpoint': 4.4.31 + '@smithy/core': 3.23.17 + '@smithy/middleware-endpoint': 4.4.32 '@smithy/middleware-stack': 4.2.14 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.14.1 - '@smithy/util-stream': 4.5.24 + '@smithy/util-stream': 4.5.25 tslib: 2.8.1 '@smithy/types@4.14.1': @@ -8193,20 +8426,20 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.3.48': + '@smithy/util-defaults-mode-browser@4.3.49': dependencies: '@smithy/property-provider': 4.2.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.2.53': + '@smithy/util-defaults-mode-node@4.2.54': dependencies: '@smithy/config-resolver': 4.4.17 '@smithy/credential-provider-imds': 4.2.14 '@smithy/node-config-provider': 4.3.14 '@smithy/property-provider': 4.2.14 - '@smithy/smithy-client': 4.12.12 + '@smithy/smithy-client': 4.12.13 '@smithy/types': 4.14.1 tslib: 2.8.1 @@ -8225,16 +8458,16 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@smithy/util-retry@4.3.3': + '@smithy/util-retry@4.3.6': dependencies: - '@smithy/service-error-classification': 4.3.0 + '@smithy/service-error-classification': 4.3.1 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@smithy/util-stream@4.5.24': + '@smithy/util-stream@4.5.25': dependencies: '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.6.0 + '@smithy/node-http-handler': 4.6.1 '@smithy/types': 4.14.1 '@smithy/util-base64': 4.3.2 '@smithy/util-buffer-from': 4.2.2 @@ -8264,15 +8497,15 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stoplight/better-ajv-errors@1.0.3(ajv@8.18.0)': + '@stoplight/better-ajv-errors@1.0.3(ajv@8.20.0)': dependencies: - ajv: 8.18.0 + ajv: 8.20.0 jsonpointer: 5.0.1 leven: 3.1.0 '@stoplight/json-ref-readers@1.2.2': dependencies: - node-fetch: 2.7.0 + node-fetch: 2.6.7 tslib: 1.14.1 transitivePeerDependencies: - encoding @@ -8286,7 +8519,7 @@ snapshots: dependency-graph: 0.11.0 fast-memoize: 2.5.2 immer: 9.0.21 - lodash: 4.17.23 + lodash: 4.18.1 tslib: 2.8.1 urijs: 1.19.11 @@ -8296,35 +8529,35 @@ snapshots: '@stoplight/path': 1.3.2 '@stoplight/types': 13.20.0 jsonc-parser: 2.2.1 - lodash: 4.17.23 + lodash: 4.18.1 safe-stable-stringify: 1.1.1 '@stoplight/ordered-object-literal@1.0.5': {} '@stoplight/path@1.3.2': {} - '@stoplight/spectral-core@1.21.0': + '@stoplight/spectral-core@1.22.0': dependencies: - '@stoplight/better-ajv-errors': 1.0.3(ajv@8.18.0) + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.20.0) '@stoplight/json': 3.21.0 '@stoplight/path': 1.3.2 '@stoplight/spectral-parsers': 1.0.5 '@stoplight/spectral-ref-resolver': 1.0.5 - '@stoplight/spectral-runtime': 1.1.4 + '@stoplight/spectral-runtime': 1.1.5 '@stoplight/types': 13.6.0 '@types/es-aggregate-error': 1.0.6 '@types/json-schema': 7.0.15 - ajv: 8.18.0 - ajv-errors: 3.0.0(ajv@8.18.0) - ajv-formats: 2.1.1(ajv@8.18.0) + ajv: 8.20.0 + ajv-errors: 3.0.0(ajv@8.20.0) + ajv-formats: 2.1.1(ajv@8.20.0) es-aggregate-error: 1.0.14 + expr-eval-fork: 3.0.3 jsonpath-plus: 10.4.0 - lodash: 4.17.23 + lodash: 4.18.1 lodash.topath: 4.5.2 - minimatch: 3.1.2 + minimatch: 3.1.5 nimma: 0.2.3 pony-cause: 1.1.1 - simple-eval: 1.0.1 tslib: 2.8.1 transitivePeerDependencies: - encoding @@ -8332,24 +8565,24 @@ snapshots: '@stoplight/spectral-formats@1.8.2': dependencies: '@stoplight/json': 3.21.0 - '@stoplight/spectral-core': 1.21.0 + '@stoplight/spectral-core': 1.22.0 '@types/json-schema': 7.0.15 tslib: 2.8.1 transitivePeerDependencies: - encoding - '@stoplight/spectral-functions@1.10.1': + '@stoplight/spectral-functions@1.10.2': dependencies: - '@stoplight/better-ajv-errors': 1.0.3(ajv@8.18.0) + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.20.0) '@stoplight/json': 3.21.0 - '@stoplight/spectral-core': 1.21.0 + '@stoplight/spectral-core': 1.22.0 '@stoplight/spectral-formats': 1.8.2 - '@stoplight/spectral-runtime': 1.1.4 - ajv: 8.18.0 - ajv-draft-04: 1.0.0(ajv@8.18.0) - ajv-errors: 3.0.0(ajv@8.18.0) - ajv-formats: 2.1.1(ajv@8.18.0) - lodash: 4.17.23 + '@stoplight/spectral-runtime': 1.1.5 + ajv: 8.20.0 + ajv-draft-04: 1.0.0(ajv@8.20.0) + ajv-errors: 3.0.0(ajv@8.20.0) + ajv-formats: 2.1.1(ajv@8.20.0) + lodash: 4.18.1 tslib: 2.8.1 transitivePeerDependencies: - encoding @@ -8365,19 +8598,19 @@ snapshots: dependencies: '@stoplight/json-ref-readers': 1.2.2 '@stoplight/json-ref-resolver': 3.1.6 - '@stoplight/spectral-runtime': 1.1.4 + '@stoplight/spectral-runtime': 1.1.5 dependency-graph: 0.11.0 tslib: 2.8.1 transitivePeerDependencies: - encoding - '@stoplight/spectral-runtime@1.1.4': + '@stoplight/spectral-runtime@1.1.5': dependencies: '@stoplight/json': 3.21.0 '@stoplight/path': 1.3.2 '@stoplight/types': 13.20.0 abort-controller: 3.0.0 - lodash: 4.17.23 + lodash: 4.18.1 node-fetch: 2.7.0 tslib: 2.8.1 transitivePeerDependencies: @@ -8411,6 +8644,8 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@telegraf/types@7.1.0': {} + '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3 @@ -8426,16 +8661,23 @@ snapshots: dependencies: '@types/estree': 1.0.8 + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.6.0 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/cookie@0.4.1': {} + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.6.0 '@types/cors@2.8.19': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/debug@4.1.13': dependencies: @@ -8445,7 +8687,7 @@ snapshots: '@types/es-aggregate-error@1.0.6': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/estree-jsx@1.0.5': dependencies: @@ -8453,14 +8695,34 @@ snapshots: '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 25.6.0 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 '@types/http-cache-semantics@4.2.0': {} + '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 25.6.0 + '@types/katex@0.16.8': {} '@types/mdast@4.0.4': @@ -8477,17 +8739,17 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/node@25.5.2': - dependencies: - undici-types: 7.18.2 - '@types/node@25.6.0': dependencies: undici-types: 7.19.2 '@types/qrcode@1.5.6': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 + + '@types/qs@6.15.0': {} + + '@types/range-parser@1.2.7': {} '@types/react@19.2.14': dependencies: @@ -8495,6 +8757,15 @@ snapshots: '@types/retry@0.12.0': {} + '@types/send@1.2.1': + dependencies: + '@types/node': 25.6.0 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.6.0 + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -8503,64 +8774,60 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/yauzl@2.10.3': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 optional: true - '@typescript/vfs@1.6.4(typescript@5.9.3)': + '@typescript/vfs@1.6.4(typescript@6.0.3)': dependencies: debug: 4.4.3 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color '@ungap/structured-clone@1.3.0': {} - '@vincentkoc/qrcode-tui@0.2.1': - dependencies: - qrcode: 1.5.4 - - '@vitest/expect@4.1.2': + '@vitest/expect@4.1.5': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.2 - '@vitest/utils': 4.1.2 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3))': + '@vitest/mocker@4.1.5(vite@7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4))': dependencies: - '@vitest/spy': 4.1.2 + '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3) + vite: 7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4) - '@vitest/pretty-format@4.1.2': + '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.2': + '@vitest/runner@4.1.5': dependencies: - '@vitest/utils': 4.1.2 + '@vitest/utils': 4.1.5 pathe: 2.0.3 - '@vitest/snapshot@4.1.2': + '@vitest/snapshot@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.2 - '@vitest/utils': 4.1.2 + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.2': {} + '@vitest/spy@4.1.5': {} - '@vitest/utils@4.1.2': + '@vitest/utils@4.1.5': dependencies: - '@vitest/pretty-format': 4.1.2 + '@vitest/pretty-format': 4.1.5 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -8603,23 +8870,23 @@ snapshots: clean-stack: 4.2.0 indent-string: 5.0.0 - ajv-draft-04@1.0.0(ajv@8.18.0): + ajv-draft-04@1.0.0(ajv@8.20.0): optionalDependencies: - ajv: 8.18.0 + ajv: 8.20.0 - ajv-errors@3.0.0(ajv@8.18.0): + ajv-errors@3.0.0(ajv@8.20.0): dependencies: - ajv: 8.18.0 + ajv: 8.20.0 - ajv-formats@2.1.1(ajv@8.18.0): + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: - ajv: 8.18.0 + ajv: 8.20.0 - ajv-formats@3.0.1(ajv@8.18.0): + ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: - ajv: 8.18.0 + ajv: 8.20.0 - ajv@8.18.0: + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 fast-uri: 3.1.0 @@ -8667,22 +8934,12 @@ snapshots: dependencies: '@ark/util': 0.55.0 - arkregex@0.0.5: - dependencies: - '@ark/util': 0.56.0 - arktype@2.1.27: dependencies: '@ark/schema': 0.55.0 '@ark/util': 0.55.0 arkregex: 0.0.3 - arktype@2.2.0: - dependencies: - '@ark/schema': 0.56.0 - '@ark/util': 0.56.0 - arkregex: 0.0.5 - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -8695,13 +8952,20 @@ snapshots: arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + asn1.js@5.4.1: + dependencies: + bn.js: 4.12.3 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + assertion-error@2.0.1: {} ast-types@0.13.4: @@ -8724,21 +8988,21 @@ snapshots: axios@1.10.0: dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - axios@1.13.2: + axios@1.15.0: dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 form-data: 4.0.5 - proxy-from-env: 1.1.0 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug - b4a@1.8.0: {} + b4a@1.8.1: {} bail@2.0.2: {} @@ -8748,24 +9012,24 @@ snapshots: bare-events@2.8.2: {} - bare-fs@4.5.6: + bare-fs@4.7.1: dependencies: bare-events: 2.8.2 bare-path: 3.0.0 - bare-stream: 2.12.0(bare-events@2.8.2) - bare-url: 2.4.0 + bare-stream: 2.13.1(bare-events@2.8.2) + bare-url: 2.4.2 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - bare-os@3.8.6: {} + bare-os@3.9.1: {} bare-path@3.0.0: dependencies: - bare-os: 3.8.6 + bare-os: 3.9.1 - bare-stream@2.12.0(bare-events@2.8.2): + bare-stream@2.13.1(bare-events@2.8.2): dependencies: streamx: 2.25.0 teex: 1.0.1 @@ -8774,7 +9038,7 @@ snapshots: transitivePeerDependencies: - react-native-b4a - bare-url@2.4.0: + bare-url@2.4.2: dependencies: bare-path: 3.0.0 @@ -8782,7 +9046,7 @@ snapshots: base64id@2.0.0: {} - basic-ftp@5.2.0: {} + basic-ftp@5.3.1: {} better-opn@3.0.2: dependencies: @@ -8799,18 +9063,20 @@ snapshots: readable-stream: 3.6.2 optional: true - body-parser@1.20.1: + bn.js@4.12.3: {} + + body-parser@1.20.5: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 + qs: 6.15.1 + raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: @@ -8824,7 +9090,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.0 + qs: 6.15.1 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -8832,14 +9098,16 @@ snapshots: boolbase@1.0.0: {} + bottleneck@2.19.5: {} + bowser@2.14.1: {} - brace-expansion@1.1.13: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.4: + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -8847,10 +9115,19 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-alloc-unsafe@1.1.0: {} + + buffer-alloc@1.2.0: + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + buffer-crc32@0.2.13: {} buffer-equal-constant-time@1.0.1: {} + buffer-fill@1.0.0: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -8858,9 +9135,9 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bundle-require@5.1.0(esbuild@0.27.3): + bundle-require@5.1.0(esbuild@0.27.7): dependencies: - esbuild: 0.27.3 + esbuild: 0.27.7 load-tsconfig: 0.2.5 bytes@3.1.2: {} @@ -8884,7 +9161,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -9067,7 +9344,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.1: {} + content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -9075,14 +9352,10 @@ snapshots: convert-to-spaces@2.0.1: {} - cookie-signature@1.0.6: {} + cookie-signature@1.0.7: {} cookie-signature@1.2.2: {} - cookie@0.4.2: {} - - cookie@0.5.0: {} - cookie@0.7.2: {} core-util-is@1.0.3: {} @@ -9092,14 +9365,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig@9.0.1(typescript@5.9.3): + cosmiconfig@9.0.1(typescript@6.0.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 croner@10.0.1: {} @@ -9245,7 +9518,7 @@ snapshots: didyoumean@1.2.2: {} - diff@8.0.3: {} + diff@8.0.4: {} dijkstrajs@1.0.3: {} @@ -9277,6 +9550,8 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dotenv@16.6.1: {} + dotenv@17.4.2: {} dunder-proto@1.0.1: @@ -9295,8 +9570,6 @@ snapshots: emoji-regex@8.0.0: {} - encodeurl@1.0.2: {} - encodeurl@2.0.0: {} end-of-stream@1.4.5: @@ -9305,18 +9578,18 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.5.5: + engine.io@6.6.7: dependencies: - '@types/cookie': 0.4.1 '@types/cors': 2.8.19 - '@types/node': 25.5.2 + '@types/node': 25.6.0 + '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 - cookie: 0.4.2 + cookie: 0.7.2 cors: 2.8.6 - debug: 4.3.7 + debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.17.1 + ws: 8.18.3 transitivePeerDependencies: - bufferutil - supports-color @@ -9336,12 +9609,12 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.24.1: + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 @@ -9360,7 +9633,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -9378,7 +9651,7 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 @@ -9397,7 +9670,7 @@ snapshots: dependencies: define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 function-bind: 1.1.2 globalthis: 1.0.4 @@ -9408,7 +9681,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@2.0.0: {} + es-module-lexer@2.1.0: {} es-object-atoms@1.1.1: dependencies: @@ -9419,7 +9692,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 es-to-primitive@1.3.0: dependencies: @@ -9427,7 +9700,7 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.45.1: {} + es-toolkit@1.46.1: {} esast-util-from-estree@2.0.0: dependencies: @@ -9443,34 +9716,34 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 - esbuild@0.27.3: + esbuild@0.27.7: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 escalade@3.2.0: {} @@ -9478,6 +9751,8 @@ snapshots: escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} escodegen@2.1.0: @@ -9531,58 +9806,64 @@ snapshots: event-target-shim@5.0.1: {} + eventemitter3@4.0.7: {} + + eventemitter3@5.0.4: {} + events-universal@1.0.1: dependencies: bare-events: 2.8.2 transitivePeerDependencies: - bare-abort-controller - eventsource-parser@3.0.6: {} + eventsource-parser@3.0.8: {} eventsource@3.0.7: dependencies: - eventsource-parser: 3.0.6 + eventsource-parser: 3.0.8 expand-template@2.0.3: optional: true expect-type@1.3.0: {} - express-rate-limit@8.3.1(express@5.2.1): + expr-eval-fork@3.0.3: {} + + express-rate-limit@8.4.1(express@5.2.1): dependencies: express: 5.2.1 ip-address: 10.1.0 - express@4.18.2: + express@4.22.0: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.1 + body-parser: 1.20.5 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.5.0 - cookie-signature: 1.0.6 + cookie: 0.7.2 + cookie-signature: 1.0.7 debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.2 fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.1 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.11.0 + qs: 6.14.2 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.2 + serve-static: 1.16.3 setprototypeof: 1.2.0 - statuses: 2.0.1 + statuses: 2.0.2 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 @@ -9593,7 +9874,7 @@ snapshots: dependencies: accepts: 2.0.0 body-parser: 2.2.2 - content-disposition: 1.0.1 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -9611,7 +9892,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.0 + qs: 6.15.1 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -9648,27 +9929,28 @@ snapshots: fast-memoize@2.5.2: {} - fast-string-truncated-width@1.2.1: {} + fast-string-truncated-width@3.0.3: {} - fast-string-width@1.1.0: + fast-string-width@3.0.2: dependencies: - fast-string-truncated-width: 1.2.1 + fast-string-truncated-width: 3.0.3 fast-uri@3.1.0: {} - fast-wrap-ansi@0.1.6: + fast-wrap-ansi@0.2.0: dependencies: - fast-string-width: 1.1.0 + fast-string-width: 3.0.2 - fast-xml-builder@1.1.4: + fast-xml-builder@1.1.5: dependencies: - path-expression-matcher: 1.2.0 + path-expression-matcher: 1.5.0 - fast-xml-parser@5.5.8: + fast-xml-parser@5.7.2: dependencies: - fast-xml-builder: 1.1.4 - path-expression-matcher: 1.2.0 - strnum: 2.2.0 + '@nodable/entities': 2.1.0 + fast-xml-builder: 1.1.5 + path-expression-matcher: 1.5.0 + strnum: 2.2.3 fastq@1.20.1: dependencies: @@ -9688,9 +9970,9 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 fetch-blob@3.2.0: dependencies: @@ -9719,14 +10001,14 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.2.0: + finalhandler@1.3.2: dependencies: debug: 2.6.9 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.1 + statuses: 2.0.2 unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -9750,10 +10032,10 @@ snapshots: fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 - mlly: 1.8.0 - rollup: 4.59.0 + mlly: 1.8.2 + rollup: 4.60.2 - follow-redirects@1.15.11: {} + follow-redirects@1.16.0: {} for-each@0.3.5: dependencies: @@ -9766,7 +10048,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 format@0.2.2: {} @@ -9791,19 +10073,19 @@ snapshots: fs-extra@11.1.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-extra@11.1.1: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-extra@11.2.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-minipass@2.1.0: @@ -9817,11 +10099,11 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.2 + hasown: 2.0.3 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -9880,7 +10162,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-nonce@1.0.1: {} @@ -9904,7 +10186,7 @@ snapshots: get-uri@6.0.5: dependencies: - basic-ftp: 5.2.0 + basic-ftp: 5.3.1 data-uri-to-buffer: 6.0.2 debug: 4.4.3 transitivePeerDependencies: @@ -9912,7 +10194,7 @@ snapshots: get-uri@8.0.0: dependencies: - basic-ftp: 5.2.0 + basic-ftp: 5.3.1 data-uri-to-buffer: 8.0.0 debug: 4.4.3 transitivePeerDependencies: @@ -9931,10 +10213,17 @@ snapshots: glob@13.0.6: dependencies: - minimatch: 10.2.4 + minimatch: 10.2.5 minipass: 7.1.3 path-scurry: 2.0.2 + global-agent@4.1.3: + dependencies: + globalthis: 1.0.4 + matcher: 4.0.0 + semver: 7.7.4 + serialize-error: 8.1.0 + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -9999,6 +10288,16 @@ snapshots: graceful-fs@4.2.11: {} + grammy@1.42.0: + dependencies: + '@grammyjs/types': 3.26.0 + abort-controller: 3.0.0 + debug: 4.4.3 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + - supports-color + gtoken@7.1.0: dependencies: gaxios: 6.7.1 @@ -10025,7 +10324,7 @@ snapshots: dependencies: has-symbols: 1.1.0 - hasown@2.0.2: + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -10183,7 +10482,7 @@ snapshots: rehype-minify-whitespace: 6.0.2 trim-trailing-lines: 2.1.0 unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 hast-util-to-string@3.0.1: dependencies: @@ -10212,11 +10511,11 @@ snapshots: highlight.js@10.7.3: {} - hono@4.12.12: {} + hono@4.12.16: {} hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.6 + lru-cache: 11.3.5 html-escaper@3.0.3: {} @@ -10231,14 +10530,6 @@ snapshots: http-cache-semantics@4.2.0: {} - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -10266,6 +10557,8 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + http_ece@1.2.0: {} + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -10327,7 +10620,7 @@ snapshots: cli-cursor: 4.0.0 cli-truncate: 4.0.0 code-excerpt: 4.0.0 - es-toolkit: 1.45.1 + es-toolkit: 1.46.1 indent-string: 5.0.0 is-in-ci: 2.0.0 patch-console: 2.0.0 @@ -10350,12 +10643,12 @@ snapshots: inline-style-parser@0.2.7: {} - inquirer@12.3.0(@types/node@25.5.2): + inquirer@12.3.0(@types/node@25.6.0): dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/prompts': 7.9.0(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) - '@types/node': 25.5.2 + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/prompts': 7.9.0(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + '@types/node': 25.6.0 ansi-escapes: 4.3.2 mute-stream: 2.0.0 run-async: 3.0.0 @@ -10364,16 +10657,18 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 + hasown: 2.0.3 side-channel: 1.1.0 ip-address@10.1.0: {} + ip-address@10.2.0: {} + ip-regex@4.3.0: {} ipaddr.js@1.9.1: {} - ipaddr.js@2.3.0: {} + ipaddr.js@2.4.0: {} is-alphabetical@2.0.1: {} @@ -10384,7 +10679,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -10417,7 +10712,7 @@ snapshots: is-core-module@2.16.1: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-data-view@1.0.2: dependencies: @@ -10434,6 +10729,8 @@ snapshots: is-docker@2.2.1: {} + is-electron@2.2.2: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -10495,7 +10792,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 is-set@2.0.3: {} @@ -10545,7 +10842,7 @@ snapshots: jiti@2.6.1: {} - jose@6.2.2: {} + jose@6.2.3: {} joycon@3.1.1: {} @@ -10572,7 +10869,7 @@ snapshots: json-schema-to-ts@3.1.1: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 ts-algebra: 2.0.0 json-schema-traverse@1.0.0: {} @@ -10583,7 +10880,7 @@ snapshots: jsonc-parser@2.2.1: {} - jsonfile@6.2.0: + jsonfile@6.2.1: dependencies: universalify: 2.0.1 optionalDependencies: @@ -10597,6 +10894,19 @@ snapshots: jsonpointer@5.0.1: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -10615,7 +10925,7 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - katex@0.16.44: + katex@0.16.45: dependencies: commander: 8.3.0 @@ -10629,7 +10939,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - koffi@2.15.1: + koffi@2.16.1: optional: true lcm@0.0.3: @@ -10668,11 +10978,25 @@ snapshots: dependencies: p-locate: 4.1.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} + lodash.topath@4.5.2: {} lodash@4.17.21: {} - lodash@4.17.23: {} + lodash@4.18.1: {} long@5.3.2: {} @@ -10684,7 +11008,7 @@ snapshots: lowercase-keys@3.0.0: {} - lru-cache@11.2.6: {} + lru-cache@11.3.5: {} lru-cache@7.18.3: {} @@ -10707,6 +11031,10 @@ snapshots: marked@15.0.12: {} + matcher@4.0.0: + dependencies: + escape-string-regexp: 4.0.0 + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -10714,7 +11042,7 @@ snapshots: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 + unist-util-visit-parents: 6.0.1 mdast-util-from-markdown@2.0.2: dependencies: @@ -10755,7 +11083,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 micromark-extension-frontmatter: 2.0.0 transitivePeerDependencies: @@ -10773,7 +11101,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: @@ -10782,7 +11110,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10792,7 +11120,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10801,14 +11129,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.1.0 mdast-util-gfm-strikethrough: 2.0.0 @@ -10836,7 +11164,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 longest-streak: 3.1.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 unist-util-remove-position: 5.0.0 transitivePeerDependencies: @@ -10848,7 +11176,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10861,7 +11189,7 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -10889,7 +11217,7 @@ snapshots: mdast-util-mdx@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.2.0 mdast-util-mdxjs-esm: 2.0.1 @@ -10903,7 +11231,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10922,7 +11250,7 @@ snapshots: micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -10934,7 +11262,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -10947,7 +11275,7 @@ snapshots: media-typer@1.1.0: {} - merge-descriptors@1.0.1: {} + merge-descriptors@1.0.3: {} merge-descriptors@2.0.0: {} @@ -11043,7 +11371,7 @@ snapshots: dependencies: '@types/katex': 0.16.8 devlop: 1.1.0 - katex: 0.16.44 + katex: 0.16.45 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -11092,8 +11420,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) + acorn: 8.11.2 + acorn-jsx: 5.3.2(acorn@8.11.2) micromark-extension-mdx-expression: 3.0.1 micromark-extension-mdx-jsx: 3.0.1 micromark-extension-mdx-md: 2.0.0 @@ -11262,16 +11590,17 @@ snapshots: mimic-response@4.0.0: {} - minimatch@10.2.4: + minimalistic-assert@1.0.1: {} + + minimatch@10.2.5: dependencies: - brace-expansion: 5.0.4 + brace-expansion: 5.0.5 - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.13 + brace-expansion: 1.1.14 - minimist@1.2.8: - optional: true + minimist@1.2.8: {} minipass@3.3.6: dependencies: @@ -11297,12 +11626,14 @@ snapshots: mkdirp@1.0.4: {} - mlly@1.8.0: + mlly@1.8.2: dependencies: acorn: 8.16.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.3 + ufo: 1.6.4 + + mri@1.2.0: {} ms@2.0.0: {} @@ -11316,7 +11647,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.11: {} + nanoid@3.3.12: {} napi-build-utils@2.0.0: optional: true @@ -11327,16 +11658,16 @@ snapshots: neotraverse@0.6.18: {} - netmask@2.0.2: {} + netmask@2.1.1: {} - next-mdx-remote-client@1.1.6(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(unified@11.0.5): + next-mdx-remote-client@1.1.7(@types/react@19.2.14)(react-dom@18.3.1(react@19.2.3))(react@19.2.3)(unified@11.0.5): dependencies: '@babel/code-frame': 7.29.0 '@mdx-js/mdx': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.3) react: 19.2.3 react-dom: 18.3.1(react@19.2.3) - remark-mdx-remove-esm: 1.2.3(unified@11.0.5) + remark-mdx-remove-esm: 1.3.1(unified@11.0.5) serialize-error: 13.0.1 vfile: 6.0.3 vfile-matter: 5.0.1 @@ -11361,14 +11692,26 @@ snapshots: node-abi@3.89.0: dependencies: - semver: 7.7.4 + semver: 7.7.2 optional: true node-addon-api@4.3.0: optional: true + node-addon-api@8.7.0: {} + node-domexception@1.0.0: {} + node-edge-tts@1.2.10: + dependencies: + https-proxy-agent: 7.0.6 + ws: 8.20.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + node-fetch@2.6.7: dependencies: whatwg-url: 5.0.0 @@ -11383,8 +11726,7 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - node-readable-to-web-readable-stream@0.4.2: - optional: true + node-gyp-build@4.8.4: {} non-error@0.1.0: {} @@ -11396,7 +11738,7 @@ snapshots: dependencies: boolbase: 1.0.0 - oauth4webapi@3.8.5: {} + oauth4webapi@3.8.6: {} object-assign@4.1.1: {} @@ -11408,7 +11750,7 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -11429,11 +11771,11 @@ snapshots: dependencies: mimic-fn: 2.1.0 - oniguruma-parser@0.12.1: {} + oniguruma-parser@0.12.2: {} - oniguruma-to-es@4.3.5: + oniguruma-to-es@4.3.6: dependencies: - oniguruma-parser: 0.12.1 + oniguruma-parser: 0.12.2 regex: 6.1.0 regex-recursion: 6.0.2 @@ -11443,78 +11785,104 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@6.26.0(ws@8.20.0)(zod@4.3.6): + openai@6.26.0(ws@8.20.0)(zod@4.4.3): optionalDependencies: ws: 8.20.0 - zod: 4.3.6 + zod: 4.4.3 - openai@6.34.0(ws@8.20.0)(zod@4.3.6): + openai@6.36.0(ws@8.20.0)(zod@4.4.3): optionalDependencies: ws: 8.20.0 - zod: 4.3.6 + zod: 4.4.3 openapi-types@12.1.3: {} - openclaw@2026.4.23(@napi-rs/canvas@0.1.97): - dependencies: - '@agentclientprotocol/sdk': 0.19.0(zod@4.3.6) - '@anthropic-ai/vertex-sdk': 0.16.0(zod@4.3.6) - '@clack/prompts': 1.2.0 - '@homebridge/ciao': 1.3.6 + openclaw@2026.5.4(@types/express@5.0.6): + dependencies: + '@agentclientprotocol/sdk': 0.21.0(zod@4.4.3) + '@anthropic-ai/sdk': 0.93.0(zod@4.4.3) + '@anthropic-ai/vertex-sdk': 0.16.0(zod@4.4.3) + '@aws-sdk/client-bedrock': 3.1042.0 + '@aws-sdk/client-bedrock-runtime': 3.1042.0 + '@aws-sdk/credential-provider-node': 3.972.39 + '@aws/bedrock-token-generator': 1.1.0 + '@clack/prompts': 1.3.0 + '@google/genai': 1.51.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)) + '@grammyjs/runner': 2.0.3(grammy@1.42.0) + '@grammyjs/transformer-throttler': 1.2.1(grammy@1.42.0) + '@homebridge/ciao': 1.3.8 '@lydell/node-pty': 1.2.0-beta.12 - '@mariozechner/pi-agent-core': 0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-coding-agent': 0.70.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6) - '@mariozechner/pi-tui': 0.70.0 - '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.6) + '@mariozechner/pi-agent-core': 0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-ai': 0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-coding-agent': 0.73.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + '@mariozechner/pi-tui': 0.73.0 + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@mozilla/readability': 0.6.0 - '@napi-rs/canvas': 0.1.97 - '@vincentkoc/qrcode-tui': 0.2.1 - ajv: 8.18.0 + '@slack/bolt': 4.7.2(@types/express@5.0.6) + '@slack/types': 2.21.0 + '@slack/web-api': 7.15.2 + ajv: 8.20.0 chalk: 5.6.2 chokidar: 5.0.0 - cli-highlight: 2.1.11 commander: 14.0.3 croner: 10.0.1 dotenv: 17.4.2 express: 5.2.1 file-type: 22.0.1 + global-agent: 4.1.3 + grammy: 1.42.0 https-proxy-agent: 9.0.0 - ipaddr.js: 2.3.0 + ipaddr.js: 2.4.0 jiti: 2.6.1 json5: 2.2.3 jszip: 3.10.1 linkedom: 0.18.12 markdown-it: 14.1.1 - openai: 6.34.0(ws@8.20.0)(zod@4.3.6) - osc-progress: 0.3.0 - pdfjs-dist: 5.6.205 + minimatch: 10.2.5 + node-edge-tts: 1.2.10 + openai: 6.36.0(ws@8.20.0)(zod@4.4.3) + openshell: 0.1.0 + pdfjs-dist: 5.7.284 + playwright-core: 1.59.1 proxy-agent: 8.0.1 - semver: 7.7.4 - sharp: 0.34.5 - sqlite-vec: 0.1.9 + qrcode: 1.5.4 tar: 7.5.13 + tokenjuice: 0.7.0 + tree-sitter-bash: 0.25.1 tslog: 4.10.2 - typebox: 1.1.28 - undici: 8.1.0 + typebox: 1.1.37 + undici: 8.2.0 + web-push: 3.6.7 + web-tree-sitter: 0.26.8 ws: 8.20.0 - yaml: 2.8.3 - zod: 4.3.6 + yaml: 2.8.4 + zod: 4.4.3 + optionalDependencies: + sqlite-vec: 0.1.9 transitivePeerDependencies: - '@cfworker/json-schema' + - '@types/express' - aws-crt - bufferutil - canvas + - debug - encoding - supports-color + - tree-sitter - utf-8-validate - openid-client@6.8.2: + openid-client@6.8.4: dependencies: - jose: 6.2.2 - oauth4webapi: 3.8.5 + jose: 6.2.3 + oauth4webapi: 3.8.6 - osc-progress@0.3.0: {} + openshell@0.1.0: + dependencies: + dotenv: 16.6.1 + telegraf: 4.16.3 + transitivePeerDependencies: + - encoding + - supports-color own-keys@1.0.1: dependencies: @@ -11529,6 +11897,8 @@ snapshots: p-cancelable@3.0.0: {} + p-finally@1.0.0: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -11537,6 +11907,11 @@ snapshots: dependencies: p-limit: 2.3.0 + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + p-retry@4.6.2: dependencies: '@types/retry': 0.12.0 @@ -11547,6 +11922,12 @@ snapshots: aggregate-error: 4.0.1 p-cancelable: 3.0.0 + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-timeout@4.1.0: {} + p-timeout@5.1.0: {} p-try@2.2.0: {} @@ -11580,12 +11961,12 @@ snapshots: pac-resolver@7.0.1: dependencies: degenerator: 5.0.1 - netmask: 2.0.2 + netmask: 2.1.1 pac-resolver@9.0.1(quickjs-wasi@2.2.0): dependencies: degenerator: 7.0.1(quickjs-wasi@2.2.0) - netmask: 2.0.2 + netmask: 2.1.1 quickjs-wasi: 2.2.0 pako@1.0.11: {} @@ -11640,7 +12021,7 @@ snapshots: path-exists@4.0.0: {} - path-expression-matcher@1.2.0: {} + path-expression-matcher@1.5.0: {} path-key@3.1.1: {} @@ -11648,19 +12029,18 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.2.6 + lru-cache: 11.3.5 minipass: 7.1.3 - path-to-regexp@0.1.7: {} + path-to-regexp@0.1.13: {} - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.2: {} pathe@2.0.3: {} - pdfjs-dist@5.6.205: + pdfjs-dist@5.7.284: optionalDependencies: - '@napi-rs/canvas': 0.1.97 - node-readable-to-web-readable-stream: 0.4.2 + '@napi-rs/canvas': 0.1.100 pend@1.2.0: {} @@ -11668,7 +12048,7 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pify@2.3.0: {} @@ -11679,9 +12059,11 @@ snapshots: pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.8.0 + mlly: 1.8.2 pathe: 2.0.3 + playwright-core@1.59.1: {} + pngjs@5.0.0: {} pony-cause@1.1.1: {} @@ -11693,7 +12075,7 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.11 + resolve: 1.22.12 postcss-js@4.1.0(postcss@8.5.6): dependencies: @@ -11707,13 +12089,13 @@ snapshots: optionalDependencies: postcss: 8.5.6 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.4): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.5.6 - yaml: 2.8.3 + yaml: 2.8.4 postcss-nested@6.2.0(postcss@8.5.6): dependencies: @@ -11727,9 +12109,15 @@ snapshots: postcss-value-parser@4.2.0: {} + postcss@8.5.13: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -11767,18 +12155,18 @@ snapshots: property-information@7.1.0: {} - protobufjs@7.5.4: + protobufjs@7.5.6: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 + '@protobufjs/codegen': 2.0.5 '@protobufjs/eventemitter': 1.1.0 '@protobufjs/fetch': 1.1.0 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 + '@protobufjs/inquire': 1.1.1 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 + '@protobufjs/utf8': 1.1.1 '@types/node': 25.6.0 long: 5.3.2 @@ -11845,10 +12233,10 @@ snapshots: - supports-color - utf-8-validate - puppeteer@22.14.0(typescript@5.9.3): + puppeteer@22.14.0(typescript@6.0.3): dependencies: '@puppeteer/browsers': 2.3.0 - cosmiconfig: 9.0.1(typescript@5.9.3) + cosmiconfig: 9.0.1(typescript@6.0.3) devtools-protocol: 0.0.1312386 puppeteer-core: 22.14.0 transitivePeerDependencies: @@ -11866,11 +12254,11 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.11.0: + qs@6.14.2: dependencies: side-channel: 1.1.0 - qs@6.15.0: + qs@6.15.1: dependencies: side-channel: 1.1.0 @@ -11882,10 +12270,10 @@ snapshots: range-parser@1.2.1: {} - raw-body@2.5.1: + raw-body@2.5.3: dependencies: bytes: 3.1.2 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 unpipe: 1.0.0 @@ -11961,7 +12349,7 @@ snapshots: readable-stream@3.6.2: dependencies: inherits: 2.0.4 - string_decoder: 1.1.1 + string_decoder: 1.3.0 util-deprecate: 1.0.2 optional: true @@ -12004,9 +12392,9 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -12025,7 +12413,7 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -12038,7 +12426,7 @@ snapshots: '@types/katex': 0.16.8 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - katex: 0.16.44 + katex: 0.16.45 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 @@ -12064,7 +12452,7 @@ snapshots: rehype-stringify@10.0.1: dependencies: '@types/hast': 3.0.4 - hast-util-to-html: 9.0.5 + hast-util-to-html: 9.0.4 unified: 11.0.5 remark-frontmatter@5.0.0: @@ -12077,6 +12465,17 @@ snapshots: - supports-color remark-gfm@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.0.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 mdast-util-gfm: 3.1.0 @@ -12096,7 +12495,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-mdx-remove-esm@1.2.3(unified@11.0.5): + remark-mdx-remove-esm@1.3.1(unified@11.0.5): dependencies: '@types/mdast': 4.0.4 mdast-util-mdxjs-esm: 2.0.1 @@ -12119,10 +12518,17 @@ snapshots: transitivePeerDependencies: - supports-color + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 + mdast-util-from-markdown: 2.0.2 micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -12136,12 +12542,20 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + remark-smartypants@3.0.2: dependencies: retext: 9.0.0 retext-smartypants: 6.2.0 unified: 11.0.5 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 remark-stringify@11.0.0: dependencies: @@ -12170,8 +12584,9 @@ snapshots: resolve-from@5.0.0: {} - resolve@1.22.11: + resolve@1.22.12: dependencies: + es-errors: 1.3.0 is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -12195,7 +12610,7 @@ snapshots: dependencies: '@types/nlcst': 2.0.3 nlcst-to-string: 4.0.0 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 retext-stringify@4.0.0: dependencies: @@ -12216,35 +12631,35 @@ snapshots: reusify@1.1.0: {} - rollup@4.59.0: + rollup@4.60.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 fsevents: 2.3.3 router@2.2.0: @@ -12253,7 +12668,7 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.2 transitivePeerDependencies: - supports-color @@ -12267,9 +12682,9 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -12279,6 +12694,10 @@ snapshots: safe-buffer@5.2.1: {} + safe-compare@1.1.4: + dependencies: + buffer-alloc: 1.2.0 + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -12294,6 +12713,8 @@ snapshots: safer-buffer@2.1.2: {} + sandwich-stream@2.0.2: {} + sax@1.6.0: {} scheduler@0.23.2: @@ -12306,21 +12727,21 @@ snapshots: semver@7.7.4: {} - send@0.18.0: + send@0.19.2: dependencies: debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 fresh: 0.5.2 - http-errors: 2.0.0 + http-errors: 2.0.1 mime: 1.6.0 ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 - statuses: 2.0.1 + statuses: 2.0.2 transitivePeerDependencies: - supports-color @@ -12343,14 +12764,18 @@ snapshots: serialize-error@13.0.1: dependencies: non-error: 0.1.0 - type-fest: 5.5.0 + type-fest: 5.6.0 - serve-static@1.15.0: + serialize-error@8.1.0: dependencies: - encodeurl: 1.0.2 + type-fest: 0.20.2 + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.18.0 + send: 0.19.2 transitivePeerDependencies: - supports-color @@ -12395,13 +12820,13 @@ snapshots: dependencies: decode-ico: 0.4.1 ico-endec: 0.1.6 - sharp: 0.34.5 + sharp: 0.33.5 sharp@0.33.5: dependencies: color: 4.2.3 detect-libc: 2.1.2 - semver: 7.7.4 + semver: 7.7.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 '@img/sharp-darwin-x64': 0.33.5 @@ -12423,37 +12848,6 @@ snapshots: '@img/sharp-win32-ia32': 0.33.5 '@img/sharp-win32-x64': 0.33.5 - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.7.4 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -12471,7 +12865,7 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -12495,7 +12889,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -12508,10 +12902,6 @@ snapshots: simple-concat@1.0.1: optional: true - simple-eval@1.0.1: - dependencies: - jsep: 1.4.0 - simple-get@4.0.1: dependencies: decompress-response: 6.0.0 @@ -12553,13 +12943,13 @@ snapshots: transitivePeerDependencies: - supports-color - socket.io@4.7.2: + socket.io@4.8.0: dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.6 debug: 4.3.7 - engine.io: 6.5.5 + engine.io: 6.6.7 socket.io-adapter: 2.5.6 socket.io-parser: 4.2.6 transitivePeerDependencies: @@ -12571,7 +12961,7 @@ snapshots: dependencies: agent-base: 9.0.0 debug: 4.4.3 - socks: 2.8.7 + socks: 2.8.8 transitivePeerDependencies: - supports-color @@ -12579,13 +12969,13 @@ snapshots: dependencies: agent-base: 7.1.4 debug: 4.4.3 - socks: 2.8.7 + socks: 2.8.8 transitivePeerDependencies: - supports-color - socks@2.8.7: + socks@2.8.8: dependencies: - ip-address: 10.1.0 + ip-address: 10.2.0 smart-buffer: 4.2.0 source-map-js@1.2.1: {} @@ -12625,6 +13015,7 @@ snapshots: sqlite-vec-linux-arm64: 0.1.9 sqlite-vec-linux-x64: 0.1.9 sqlite-vec-windows-x64: 0.1.9 + optional: true stack-utils@2.0.6: dependencies: @@ -12632,13 +13023,11 @@ snapshots: stackback@0.0.2: {} - statuses@2.0.1: {} - statuses@2.0.2: {} std-env@3.10.0: {} - std-env@4.0.0: {} + std-env@4.1.0: {} stop-iteration-iterator@1.1.0: dependencies: @@ -12668,24 +13057,24 @@ snapshots: string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -12693,6 +13082,11 @@ snapshots: dependencies: safe-buffer: 5.1.2 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -12709,7 +13103,7 @@ snapshots: strip-json-comments@2.0.1: optional: true - strnum@2.2.0: {} + strnum@2.2.3: {} strtok3@10.3.5: dependencies: @@ -12730,7 +13124,7 @@ snapshots: lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 ts-interface-checker: 0.1.13 supports-color@7.2.0: @@ -12741,7 +13135,7 @@ snapshots: tagged-tag@1.0.0: {} - tailwindcss@3.4.19(yaml@2.8.3): + tailwindcss@3.4.19(yaml@2.8.4): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -12760,10 +13154,10 @@ snapshots: postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) postcss-js: 4.1.0(postcss@8.5.6) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.3) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.4) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 - resolve: 1.22.11 + resolve: 1.22.12 sucrase: 3.35.1 transitivePeerDependencies: - tsx @@ -12791,7 +13185,7 @@ snapshots: postcss-load-config: 4.0.2(postcss@8.5.6) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 - resolve: 1.22.11 + resolve: 1.22.12 sucrase: 3.35.1 transitivePeerDependencies: - ts-node @@ -12807,9 +13201,9 @@ snapshots: tar-fs@3.1.2: dependencies: pump: 3.0.4 - tar-stream: 3.1.8 + tar-stream: 3.2.0 optionalDependencies: - bare-fs: 4.5.6 + bare-fs: 4.7.1 bare-path: 3.0.0 transitivePeerDependencies: - bare-abort-controller @@ -12825,10 +13219,10 @@ snapshots: readable-stream: 3.6.2 optional: true - tar-stream@3.1.8: + tar-stream@3.2.0: dependencies: - b4a: 1.8.0 - bare-fs: 4.5.6 + b4a: 1.8.1 + bare-fs: 4.7.1 fast-fifo: 1.3.2 streamx: 2.25.0 transitivePeerDependencies: @@ -12860,9 +13254,23 @@ snapshots: - bare-abort-controller - react-native-b4a + telegraf@4.16.3: + dependencies: + '@telegraf/types': 7.1.0 + abort-controller: 3.0.0 + debug: 4.4.3 + mri: 1.2.0 + node-fetch: 2.7.0 + p-timeout: 4.1.0 + safe-compare: 1.1.4 + sandwich-stream: 2.0.2 + transitivePeerDependencies: + - encoding + - supports-color + text-decoder@1.2.7: dependencies: - b4a: 1.8.0 + b4a: 1.8.1 transitivePeerDependencies: - react-native-b4a @@ -12880,12 +13288,12 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.0.4: {} + tinyexec@1.1.2: {} - tinyglobby@0.2.15: + tinyglobby@0.2.16: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinyrainbow@3.1.0: {} @@ -12899,14 +13307,21 @@ snapshots: token-types@6.1.2: dependencies: - '@borewit/text-codec': 0.2.1 + '@borewit/text-codec': 0.2.2 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + tokenjuice@0.7.0: {} + tr46@0.0.3: {} tree-kill@1.2.2: {} + tree-sitter-bash@0.25.1: + dependencies: + node-addon-api: 8.7.0 + node-gyp-build: 4.8.4 + trim-lines@3.0.1: {} trim-trailing-lines@2.1.0: {} @@ -12923,28 +13338,30 @@ snapshots: tslog@4.10.2: {} - tsup@8.5.1(jiti@1.21.7)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.3): + tsscmp@1.0.6: {} + + tsup@8.5.1(jiti@1.21.7)(postcss@8.5.6)(typescript@6.0.3)(yaml@2.8.4): dependencies: - bundle-require: 5.1.0(esbuild@0.27.3) + bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 debug: 4.4.3 - esbuild: 0.27.3 + esbuild: 0.27.7 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.3) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.4) resolve-from: 5.0.0 - rollup: 4.59.0 + rollup: 4.60.2 source-map: 0.7.6 sucrase: 3.35.1 tinyexec: 0.3.2 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 tree-kill: 1.2.2 optionalDependencies: postcss: 8.5.6 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - jiti - supports-color @@ -12956,21 +13373,23 @@ snapshots: safe-buffer: 5.2.1 optional: true - twoslash-protocol@0.3.6: {} + twoslash-protocol@0.3.8: {} - twoslash@0.3.6(typescript@5.9.3): + twoslash@0.3.8(typescript@6.0.3): dependencies: - '@typescript/vfs': 1.6.4(typescript@5.9.3) - twoslash-protocol: 0.3.6 - typescript: 5.9.3 + '@typescript/vfs': 1.6.4(typescript@6.0.3) + twoslash-protocol: 0.3.8 + typescript: 6.0.3 transitivePeerDependencies: - supports-color + type-fest@0.20.2: {} + type-fest@0.21.3: {} type-fest@4.41.0: {} - type-fest@5.5.0: + type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 @@ -12985,7 +13404,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typebox@1.1.28: {} + typebox@1.1.37: {} typed-array-buffer@1.0.3: dependencies: @@ -12995,7 +13414,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -13004,7 +13423,7 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -13013,18 +13432,18 @@ snapshots: typed-array-length@1.0.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@5.9.3: {} + typescript@6.0.3: {} uc.micro@2.1.0: {} - ufo@1.6.3: {} + ufo@1.6.4: {} uhyphen@0.2.0: {} @@ -13042,13 +13461,11 @@ snapshots: buffer: 5.7.1 through: 2.3.8 - undici-types@7.18.2: {} - undici-types@7.19.2: {} - undici@7.24.6: {} + undici@7.25.0: {} - undici@8.1.0: {} + undici@8.2.0: {} unified@11.0.5: dependencies: @@ -13097,13 +13514,13 @@ snapshots: unist-util-remove-position@5.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-visit: 5.1.0 + unist-util-visit: 5.0.0 unist-util-remove@4.0.0: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 + unist-util-visit-parents: 6.0.1 unist-util-stringify-position@4.0.0: dependencies: @@ -13140,12 +13557,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - universalify@2.0.1: {} unpipe@1.0.0: {} @@ -13203,51 +13614,63 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3): + vite@7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.59.0 - tinyglobby: 0.2.15 + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.13 + rollup: 4.60.2 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 fsevents: 2.3.3 jiti: 1.21.7 - yaml: 2.8.3 - - vitest@4.1.2(@types/node@25.5.2)(vite@7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.2 - '@vitest/runner': 4.1.2 - '@vitest/snapshot': 4.1.2 - '@vitest/spy': 4.1.2 - '@vitest/utils': 4.1.2 - es-module-lexer: 2.0.0 + yaml: 2.8.4 + + vitest@4.1.5(@types/node@25.6.0)(vite@7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 4.0.0 + picomatch: 4.0.4 + std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 1.0.4 - tinyglobby: 0.2.15 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 7.3.1(@types/node@25.5.2)(jiti@1.21.7)(yaml@2.8.3) + vite: 7.3.1(@types/node@25.6.0)(jiti@1.21.7)(yaml@2.8.4) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 transitivePeerDependencies: - msw web-namespaces@2.0.1: {} + web-push@3.6.7: + dependencies: + asn1.js: 5.4.1 + http_ece: 1.2.0 + https-proxy-agent: 7.0.6 + jws: 4.0.1 + minimist: 1.2.8 + transitivePeerDependencies: + - supports-color + web-streams-polyfill@3.3.3: {} + web-tree-sitter@0.26.8: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -13291,7 +13714,7 @@ snapshots: which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -13331,8 +13754,6 @@ snapshots: wrappy@1.0.2: {} - ws@8.17.1: {} - ws@8.18.3: {} ws@8.20.0: {} @@ -13359,6 +13780,8 @@ snapshots: yaml@2.8.3: {} + yaml@2.8.4: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 @@ -13431,9 +13854,9 @@ snapshots: dependencies: zod: 3.24.0 - zod-to-json-schema@3.25.1(zod@4.3.6): + zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: - zod: 4.3.6 + zod: 4.4.3 zod@3.21.4: {} @@ -13441,6 +13864,6 @@ snapshots: zod@3.24.0: {} - zod@4.3.6: {} + zod@4.4.3: {} zwitch@2.0.4: {} diff --git a/scripts/write-plugin-manifest.mjs b/scripts/write-plugin-manifest.mjs index a08c773..b347d93 100644 --- a/scripts/write-plugin-manifest.mjs +++ b/scripts/write-plugin-manifest.mjs @@ -21,10 +21,7 @@ const channelSchema = channelPlugin?.configSchema; const existingChannelConfig = manifest.channelConfigs?.[channelId] ?? {}; function mergeUiHints(runtimeUiHints, existingUiHints) { - const keys = new Set([ - ...Object.keys(existingUiHints ?? {}), - ...Object.keys(runtimeUiHints ?? {}), - ]); + const keys = Object.keys(runtimeUiHints ?? {}); return Object.fromEntries( [...keys].map((key) => [ @@ -44,6 +41,10 @@ if (!channelId || !channelSchema?.schema || typeof channelSchema.schema !== "obj const packageChannelMeta = packageJson?.openclaw?.channel?.id === channelId ? packageJson.openclaw.channel : {}; +if (packageJson?.description) { + manifest.description = packageJson.description; +} + manifest.channelConfigs = { ...(manifest.channelConfigs ?? {}), [channelId]: { diff --git a/src/actions/actions.test.ts b/src/actions/actions.test.ts index b0f10bc..dd1e47e 100644 --- a/src/actions/actions.test.ts +++ b/src/actions/actions.test.ts @@ -8,9 +8,7 @@ describe("simplex message tool discovery", () => { const cfg = { channels: { "openclaw-simplex": { - connection: { - wsUrl: "ws://127.0.0.1:5225", - }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, }, }, } as OpenClawConfig; @@ -49,11 +47,14 @@ describe("simplex message tool discovery", () => { mediaUrl: expect.any(Object), filePath: expect.any(Object), caption: expect.any(Object), + pollQuestion: expect.any(Object), + pollOption: expect.any(Object), emoji: expect.any(Object), displayName: expect.any(Object), participant: expect.any(Object), }), }); + expect(simplexMessageActions.supportsAction?.({ action: "poll" })).toBe(true); }); it("returns null when no configured accounts are available", () => { @@ -107,7 +108,7 @@ describe("simplex message tool discovery", () => { reactionLevel: "ack", accounts: { work: { - connection: { wsUrl: "ws://127.0.0.1:6225" }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, reactionLevel: "minimal", }, }, diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 735839a..3e84587 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -1,4 +1,5 @@ import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract"; +import { resolveSimplexRouteTarget } from "../channel/shared/simplex-common.js"; import { describeSimplexMessageActions } from "./discovery.js"; import { executeSimplexAction } from "./execute.js"; import { buildSimplexMessageToolSchema, SIMPLEX_SUPPORTED_ACTIONS } from "./schema.js"; @@ -17,9 +18,8 @@ function extractSimplexToolSend(args: Record) { return null; } const accountId = readString(args.accountId); - const threadId = - typeof args.threadId === "number" ? String(args.threadId) : readString(args.threadId); - return { to, accountId, threadId }; + const threadId = typeof args.threadId === "number" ? args.threadId : readString(args.threadId); + return resolveSimplexRouteTarget({ rawTarget: to, accountId, fallbackThreadId: threadId }); } export const simplexMessageActions: ChannelMessageActionAdapter = { diff --git a/src/actions/discovery.ts b/src/actions/discovery.ts index cdece0a..c46b69c 100644 --- a/src/actions/discovery.ts +++ b/src/actions/discovery.ts @@ -1,10 +1,8 @@ import { createActionGate } from "openclaw/plugin-sdk/channel-actions"; import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import { type ReactionLevel, resolveReactionLevel } from "openclaw/plugin-sdk/text-runtime"; +import { resolveReactionLevel } from "openclaw/plugin-sdk/text-runtime"; import { listEnabledSimplexAccounts, resolveSimplexAccount } from "../config/accounts.js"; -export type SimplexReactionLevel = ReactionLevel; - function areSimplexPollsEnabled(params: { cfg: OpenClawConfig; accountId?: string | null }) { const account = resolveSimplexAccount(params); return createActionGate(account.config.actions)("polls"); diff --git a/src/actions/execute.ts b/src/actions/execute.ts index 24a4541..1db4096 100644 --- a/src/actions/execute.ts +++ b/src/actions/execute.ts @@ -1,105 +1,11 @@ import type { ChannelMessageActionName } from "openclaw/plugin-sdk/channel-contract"; import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import { buildComposedMessages } from "../channel/simplex-media.js"; import { resolveSimplexAccount } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { - buildAddGroupMemberCommand, - buildDeleteChatItemCommand, - buildLeaveGroupCommand, - buildReactionCommand, - buildRemoveGroupMemberCommand, - buildSendMessagesCommand, - buildUpdateChatItemCommand, - buildUpdateGroupProfileCommand, - type SimplexComposedMessage, -} from "../simplex/simplex-commands.js"; -import { resolveSimplexCommandError } from "../simplex/simplex-errors.js"; -import { SimplexWsClient } from "../simplex/simplex-ws-client.js"; -import { assertSimplexReactActionAllowed } from "./discovery.js"; -import { - normalizeSimplexGroupRef, - readChatRef, - readDeleteMode, - readGroupTarget, - readMessageIds, - readNumberParam, - readStringParam, - readUploadMediaUrl, -} from "./params.js"; -import { jsonResult } from "./result.js"; +import type { SimplexActionParams, ToolResult } from "../types/actions.js"; +import { executeSimplexGroupAction } from "./group-actions.js"; +import { executeSimplexMessageAction } from "./message-actions.js"; +import { readChatRef } from "./params.js"; import { SIMPLEX_SUPPORTED_ACTIONS } from "./schema.js"; -import type { SimplexActionParams, ToolResult } from "./types.js"; - -async function withSimplexClient( - account: ResolvedSimplexAccount, - fn: (client: SimplexWsClient) => Promise -): Promise { - const client = new SimplexWsClient({ - url: account.wsUrl, - connectTimeoutMs: account.config.connection?.connectTimeoutMs, - }); - await client.connect(); - try { - return await fn(client); - } finally { - await client.close(); - } -} - -async function resolveEditMessage(params: { - cfg: OpenClawConfig; - account: ResolvedSimplexAccount; - text: string; -}): Promise { - const composed = await buildComposedMessages({ - cfg: params.cfg, - accountId: params.account.accountId, - text: params.text, - }); - if (composed.length === 0) { - throw new Error("text required"); - } - const first = composed[0]; - if (!first) { - throw new Error("text required"); - } - return first; -} - -async function sendActionComposedMessages(params: { - account: ResolvedSimplexAccount; - chatRef: string; - composedMessages: SimplexComposedMessage[]; -}): Promise<{ messageId?: string }> { - if (params.composedMessages.length === 0) { - return {}; - } - const cmd = buildSendMessagesCommand({ - chatRef: params.chatRef, - composedMessages: params.composedMessages, - }); - const response = await withSimplexClient(params.account, (client) => client.sendCommand(cmd)); - const resp = response.resp as { - type?: string; - chatError?: { errorType?: { type?: string; message?: string } }; - chatItems?: Array<{ chatItem?: { meta?: { itemId?: unknown } } }>; - itemId?: unknown; - messageId?: unknown; - }; - const commandError = resolveSimplexCommandError(resp); - if (commandError) { - throw new Error(commandError); - } - const rawMessageId = resp.chatItems?.[0]?.chatItem?.meta?.itemId ?? resp.messageId ?? resp.itemId; - if (typeof rawMessageId === "number" && Number.isFinite(rawMessageId)) { - return { messageId: String(rawMessageId) }; - } - if (typeof rawMessageId === "string" && rawMessageId.trim()) { - return { messageId: rawMessageId.trim() }; - } - return {}; -} export async function executeSimplexAction(params: { action: ChannelMessageActionName; @@ -127,175 +33,24 @@ export async function executeSimplexAction(params: { } const chatRef = readChatRef(toolParams); - - if (action === "upload-file") { - const mediaUrl = readUploadMediaUrl(toolParams); - if (!mediaUrl) { - throw new Error("mediaUrl, media, filePath, or path required"); - } - const text = - readStringParam(toolParams, "text", { allowEmpty: true }) ?? - readStringParam(toolParams, "message", { allowEmpty: true }) ?? - readStringParam(toolParams, "caption", { allowEmpty: true }) ?? - ""; - const audioAsVoice = - typeof toolParams.audioAsVoice === "boolean" - ? toolParams.audioAsVoice - : typeof toolParams.asVoice === "boolean" - ? toolParams.asVoice - : undefined; - const composedMessages = await buildComposedMessages({ - cfg, - accountId: account.accountId, - text, - mediaUrl, - audioAsVoice, - }); - const result = await sendActionComposedMessages({ account, chatRef, composedMessages }); - return jsonResult({ - ok: true, - uploaded: true, - to: chatRef, - mediaUrl, - messageId: result.messageId ?? null, - }); - } - - if (action === "react") { - assertSimplexReactActionAllowed({ cfg, accountId: account.accountId }); - const messageId = - readNumberParam(toolParams, "messageId", { integer: true }) ?? - readNumberParam(toolParams, "chatItemId", { integer: true }); - if (messageId === undefined) { - throw new Error("messageId required"); - } - const emoji = readStringParam(toolParams, "emoji", { allowEmpty: true }); - const remove = typeof toolParams.remove === "boolean" ? toolParams.remove : false; - const reaction = - typeof toolParams.reaction === "object" && toolParams.reaction !== null - ? (toolParams.reaction as Record) - : emoji - ? { emoji } - : null; - if (!reaction) { - throw new Error("reaction or emoji required"); - } - const cmd = buildReactionCommand({ - chatRef, - chatItemId: messageId, - add: !remove, - reaction, - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, action: remove ? "removed" : "added", emoji }); - } - - if (action === "edit") { - const messageId = - readNumberParam(toolParams, "messageId", { integer: true }) ?? - readNumberParam(toolParams, "chatItemId", { integer: true }); - if (messageId === undefined) { - throw new Error("messageId required"); - } - const text = - readStringParam(toolParams, "text", { allowEmpty: false }) ?? - readStringParam(toolParams, "message", { allowEmpty: false }); - if (!text) { - throw new Error("text required"); - } - const updatedMessage = await resolveEditMessage({ cfg, account, text }); - const cmd = buildUpdateChatItemCommand({ - chatRef, - chatItemId: messageId, - updatedMessage, - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, updated: messageId }); - } - - if (action === "delete" || action === "unsend") { - const messageIds = readMessageIds(toolParams); - const cmd = buildDeleteChatItemCommand({ - chatRef, - chatItemIds: messageIds, - deleteMode: readDeleteMode(toolParams), - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, deleted: messageIds }); - } - - if (action === "renameGroup") { - const target = readGroupTarget(toolParams); - const rawProfile = - readStringParam(toolParams, "profile") ?? readStringParam(toolParams, "groupProfile"); - if (rawProfile) { - let profile: Record; - try { - profile = JSON.parse(rawProfile) as Record; - } catch (err) { - throw new Error(`Invalid profile JSON: ${String(err)}`, { cause: err }); - } - const cmd = buildUpdateGroupProfileCommand({ - groupId: normalizeSimplexGroupRef(target), - profile, - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, group: target, profile }); - } - const displayName = - readStringParam(toolParams, "displayName") ?? - readStringParam(toolParams, "name") ?? - readStringParam(toolParams, "title"); - if (!displayName) { - throw new Error("displayName or name required"); - } - const cmd = buildUpdateGroupProfileCommand({ - groupId: normalizeSimplexGroupRef(target), - profile: { displayName }, - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, group: target, displayName }); - } - - if (action === "addParticipant") { - const target = readGroupTarget(toolParams); - const participant = - readStringParam(toolParams, "participant") ?? - readStringParam(toolParams, "contactId") ?? - readStringParam(toolParams, "memberId"); - if (!participant) { - throw new Error("participant or contactId required"); - } - const cmd = buildAddGroupMemberCommand({ - groupId: normalizeSimplexGroupRef(target), - contactId: participant, - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, group: target, added: participant }); - } - - if (action === "removeParticipant") { - const target = readGroupTarget(toolParams); - const participant = - readStringParam(toolParams, "participant") ?? - readStringParam(toolParams, "memberId") ?? - readStringParam(toolParams, "contactId"); - if (!participant) { - throw new Error("participant or memberId required"); - } - const cmd = buildRemoveGroupMemberCommand({ - groupId: normalizeSimplexGroupRef(target), - memberId: participant, - }); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, group: target, removed: participant }); + const messageResult = await executeSimplexMessageAction({ + action, + cfg, + account, + chatRef, + toolParams, + }); + if (messageResult) { + return messageResult; } - if (action === "leaveGroup") { - const target = readGroupTarget(toolParams); - const cmd = buildLeaveGroupCommand(normalizeSimplexGroupRef(target)); - await withSimplexClient(account, (client) => client.sendCommand(cmd)); - return jsonResult({ ok: true, group: target, left: true }); + const groupResult = await executeSimplexGroupAction({ + action, + account, + toolParams, + }); + if (groupResult) { + return groupResult; } throw new Error(`Action ${action} not supported for simplex.`); diff --git a/src/actions/group-actions.ts b/src/actions/group-actions.ts new file mode 100644 index 0000000..894cf91 --- /dev/null +++ b/src/actions/group-actions.ts @@ -0,0 +1,113 @@ +import type { ChannelMessageActionName } from "openclaw/plugin-sdk/channel-contract"; +import { parseSimplexNumericId } from "../simplex/runtime/api.js"; +import { withSimplexClient } from "../simplex/runtime/transport.js"; +import type { SimplexActionParams, ToolResult } from "../types/actions.js"; +import type { ResolvedSimplexAccount } from "../types/config.js"; +import type { SimplexGroupProfile } from "../types/simplex.js"; +import { normalizeSimplexGroupRef, readGroupTarget, readStringParam } from "./params.js"; +import { jsonResult } from "./result.js"; + +export async function executeSimplexGroupAction(params: { + action: ChannelMessageActionName; + account: ResolvedSimplexAccount; + toolParams: SimplexActionParams; +}): Promise { + const { action, account, toolParams } = params; + + if (action === "renameGroup") { + const target = readGroupTarget(toolParams); + const rawProfile = + readStringParam(toolParams, "profile") ?? readStringParam(toolParams, "groupProfile"); + if (rawProfile) { + let profile: Record; + try { + profile = JSON.parse(rawProfile) as Record; + } catch (err) { + throw new Error(`Invalid profile JSON: ${String(err)}`, { cause: err }); + } + const groupId = parseSimplexNumericId(normalizeSimplexGroupRef(target)); + if (groupId === null) { + throw new Error(`SimpleX group id must be numeric for runtime API: ${target}`); + } + await withSimplexClient({ + account, + run: (client) => + client.updateGroupProfile({ groupId, profile: profile as Partial }), + }); + return jsonResult({ ok: true, group: target, profile }); + } + const displayName = + readStringParam(toolParams, "displayName") ?? + readStringParam(toolParams, "name") ?? + readStringParam(toolParams, "title"); + if (!displayName) { + throw new Error("displayName or name required"); + } + const groupId = parseSimplexNumericId(normalizeSimplexGroupRef(target)); + if (groupId === null) { + throw new Error(`SimpleX group id must be numeric for runtime API: ${target}`); + } + await withSimplexClient({ + account, + run: (client) => client.updateGroupProfile({ groupId, profile: { displayName } }), + }); + return jsonResult({ ok: true, group: target, displayName }); + } + + if (action === "addParticipant") { + const target = readGroupTarget(toolParams); + const participant = + readStringParam(toolParams, "participant") ?? + readStringParam(toolParams, "contactId") ?? + readStringParam(toolParams, "memberId"); + if (!participant) { + throw new Error("participant or contactId required"); + } + const groupId = parseSimplexNumericId(normalizeSimplexGroupRef(target)); + const contactId = parseSimplexNumericId(participant); + if (groupId === null || contactId === null) { + throw new Error("SimpleX group and contact ids must be numeric for runtime API"); + } + await withSimplexClient({ + account, + run: (client) => client.addGroupMember({ groupId, contactId }), + }); + return jsonResult({ ok: true, group: target, added: participant }); + } + + if (action === "removeParticipant") { + const target = readGroupTarget(toolParams); + const participant = + readStringParam(toolParams, "participant") ?? + readStringParam(toolParams, "memberId") ?? + readStringParam(toolParams, "contactId"); + if (!participant) { + throw new Error("participant or memberId required"); + } + const groupId = parseSimplexNumericId(normalizeSimplexGroupRef(target)); + const memberId = parseSimplexNumericId(participant); + if (groupId === null || memberId === null) { + throw new Error("SimpleX group and member ids must be numeric for runtime API"); + } + await withSimplexClient({ + account, + run: (client) => client.removeGroupMember({ groupId, memberId }), + }); + return jsonResult({ ok: true, group: target, removed: participant }); + } + + if (action === "leaveGroup") { + const target = readGroupTarget(toolParams); + const groupId = parseSimplexNumericId(normalizeSimplexGroupRef(target)); + if (groupId === null) { + throw new Error(`SimpleX group id must be numeric for runtime API: ${target}`); + } + await withSimplexClient({ + account, + run: (client) => client.leaveGroup(groupId), + }); + return jsonResult({ ok: true, group: target, left: true }); + } + + return null; +} diff --git a/src/actions/message-actions.ts b/src/actions/message-actions.ts new file mode 100644 index 0000000..663da88 --- /dev/null +++ b/src/actions/message-actions.ts @@ -0,0 +1,214 @@ +import { readStringArrayParam } from "openclaw/plugin-sdk/channel-actions"; +import type { ChannelMessageActionName } from "openclaw/plugin-sdk/channel-contract"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { normalizePollInput, resolvePollMaxSelections } from "openclaw/plugin-sdk/poll-runtime"; +import { buildComposedMessages } from "../channel/media/simplex-media.js"; +import { renderSimplexPollText } from "../channel/messaging/simplex-outbound.js"; +import { resolveSimplexChatItemId } from "../simplex/runtime/api.js"; +import { withSimplexClient } from "../simplex/runtime/transport.js"; +import type { SimplexActionParams, ToolResult } from "../types/actions.js"; +import type { ResolvedSimplexAccount } from "../types/config.js"; +import type { + SimplexComposedMessage, + SimplexDeleteMode, + SimplexReaction, +} from "../types/simplex.js"; +import { assertSimplexReactActionAllowed } from "./discovery.js"; +import { + readDeleteMode, + readMessageIds, + readNumberParam, + readStringParam, + readUploadMediaUrl, +} from "./params.js"; +import { jsonResult } from "./result.js"; + +async function resolveEditMessage(params: { + cfg: OpenClawConfig; + account: ResolvedSimplexAccount; + text: string; +}): Promise { + const composed = await buildComposedMessages({ + cfg: params.cfg, + accountId: params.account.accountId, + text: params.text, + }); + if (composed.length === 0) { + throw new Error("text required"); + } + const first = composed[0]; + if (!first) { + throw new Error("text required"); + } + return first; +} + +async function sendActionComposedMessages(params: { + account: ResolvedSimplexAccount; + chatRef: string; + composedMessages: SimplexComposedMessage[]; +}): Promise<{ messageId?: string }> { + if (params.composedMessages.length === 0) { + return {}; + } + const chatItems = await withSimplexClient({ + account: params.account, + run: (client) => + client.sendMessages({ + chatRef: params.chatRef, + composedMessages: params.composedMessages, + }), + }); + return { messageId: resolveSimplexChatItemId(chatItems[0]) }; +} + +export async function executeSimplexMessageAction(params: { + action: ChannelMessageActionName; + cfg: OpenClawConfig; + account: ResolvedSimplexAccount; + chatRef: string; + toolParams: SimplexActionParams; +}): Promise { + const { action, cfg, account, chatRef, toolParams } = params; + + if (action === "poll") { + const question = + readStringParam(toolParams, "pollQuestion", { allowEmpty: false }) ?? + readStringParam(toolParams, "question", { allowEmpty: false }); + if (!question) { + throw new Error("pollQuestion required"); + } + const options = readStringArrayParam(toolParams, "pollOption", { required: true }) ?? []; + if (options.length < 2) { + throw new Error("pollOption requires at least two values"); + } + const allowMultiselect = + typeof toolParams.pollMulti === "boolean" ? toolParams.pollMulti : false; + const durationHours = readNumberParam(toolParams, "pollDurationHours", { integer: true }); + const normalized = normalizePollInput({ + question, + options, + maxSelections: resolvePollMaxSelections(options.length, allowMultiselect), + durationHours, + }); + const composedMessages = await buildComposedMessages({ + cfg, + accountId: account.accountId, + text: renderSimplexPollText(normalized), + }); + const result = await sendActionComposedMessages({ account, chatRef, composedMessages }); + return jsonResult({ ok: true, poll: true, to: chatRef, messageId: result.messageId ?? null }); + } + + if (action === "upload-file") { + const mediaUrl = readUploadMediaUrl(toolParams); + if (!mediaUrl) { + throw new Error("mediaUrl, media, filePath, or path required"); + } + const text = + readStringParam(toolParams, "text", { allowEmpty: true }) ?? + readStringParam(toolParams, "message", { allowEmpty: true }) ?? + readStringParam(toolParams, "caption", { allowEmpty: true }) ?? + ""; + const audioAsVoice = + typeof toolParams.audioAsVoice === "boolean" + ? toolParams.audioAsVoice + : typeof toolParams.asVoice === "boolean" + ? toolParams.asVoice + : undefined; + const composedMessages = await buildComposedMessages({ + cfg, + accountId: account.accountId, + text, + mediaUrl, + audioAsVoice, + }); + const result = await sendActionComposedMessages({ account, chatRef, composedMessages }); + return jsonResult({ + ok: true, + uploaded: true, + to: chatRef, + mediaUrl, + messageId: result.messageId ?? null, + }); + } + + if (action === "react") { + assertSimplexReactActionAllowed({ cfg, accountId: account.accountId }); + const messageId = + readNumberParam(toolParams, "messageId", { integer: true }) ?? + readNumberParam(toolParams, "chatItemId", { integer: true }); + if (messageId === undefined) { + throw new Error("messageId required"); + } + const emoji = readStringParam(toolParams, "emoji", { allowEmpty: true }); + const remove = typeof toolParams.remove === "boolean" ? toolParams.remove : false; + const reaction = + typeof toolParams.reaction === "object" && toolParams.reaction !== null + ? (toolParams.reaction as Record) + : emoji + ? { emoji } + : null; + if (!reaction) { + throw new Error("reaction or emoji required"); + } + const runtimeReaction = + emoji && !("type" in reaction) + ? ({ type: "emoji", emoji } as SimplexReaction) + : (reaction as SimplexReaction); + await withSimplexClient({ + account, + run: (client) => + client.reactToMessage({ + chatRef, + messageId, + add: !remove, + reaction: runtimeReaction, + }), + }); + return jsonResult({ ok: true, action: remove ? "removed" : "added", emoji }); + } + + if (action === "edit") { + const messageId = + readNumberParam(toolParams, "messageId", { integer: true }) ?? + readNumberParam(toolParams, "chatItemId", { integer: true }); + if (messageId === undefined) { + throw new Error("messageId required"); + } + const text = + readStringParam(toolParams, "text", { allowEmpty: false }) ?? + readStringParam(toolParams, "message", { allowEmpty: false }); + if (!text) { + throw new Error("text required"); + } + const updatedMessage = await resolveEditMessage({ cfg, account, text }); + await withSimplexClient({ + account, + run: (client) => + client.editMessage({ + chatRef, + messageId, + updatedMessage, + }), + }); + return jsonResult({ ok: true, updated: messageId }); + } + + if (action === "delete" || action === "unsend") { + const messageIds = readMessageIds(toolParams); + const deleteMode = readDeleteMode(toolParams); + await withSimplexClient({ + account, + run: (client) => + client.deleteMessages({ + chatRef, + messageIds, + deleteMode: (deleteMode ?? "broadcast") as SimplexDeleteMode, + }), + }); + return jsonResult({ ok: true, deleted: messageIds }); + } + + return null; +} diff --git a/src/actions/params.ts b/src/actions/params.ts index 67b19a0..626715a 100644 --- a/src/actions/params.ts +++ b/src/actions/params.ts @@ -1,5 +1,5 @@ -import { stripSimplexPrefix } from "../channel/simplex-common.js"; -import type { DeleteMode, SimplexActionParams } from "./types.js"; +import { stripSimplexPrefix } from "../channel/shared/simplex-common.js"; +import type { DeleteMode, SimplexActionParams } from "../types/actions.js"; export function readStringParam( params: SimplexActionParams, diff --git a/src/actions/result.ts b/src/actions/result.ts index c2fc6c2..c50cfb5 100644 --- a/src/actions/result.ts +++ b/src/actions/result.ts @@ -1,4 +1,4 @@ -import type { ToolResult } from "./types.js"; +import type { ToolResult } from "../types/actions.js"; export function jsonResult(payload: unknown): ToolResult { return { diff --git a/src/actions/schema.ts b/src/actions/schema.ts index 26b7feb..e8fb788 100644 --- a/src/actions/schema.ts +++ b/src/actions/schema.ts @@ -5,6 +5,7 @@ import type { } from "openclaw/plugin-sdk/channel-contract"; export const SIMPLEX_SUPPORTED_ACTIONS = new Set([ + "poll", "upload-file", "react", "edit", @@ -16,19 +17,6 @@ export const SIMPLEX_SUPPORTED_ACTIONS = new Set([ "leaveGroup", ]); -export const SIMPLEX_MESSAGE_TOOL_ACTIONS = [ - "send", - "upload-file", - "react", - "edit", - "delete", - "unsend", - "renameGroup", - "addParticipant", - "removeParticipant", - "leaveGroup", -] as const satisfies readonly ChannelMessageActionName[]; - export function buildSimplexMessageToolSchema(): ChannelMessageToolSchemaContribution { return { properties: { @@ -142,6 +130,26 @@ export function buildSimplexMessageToolSchema(): ChannelMessageToolSchemaContrib description: "Alias for audioAsVoice.", }) ), + pollQuestion: Type.Optional( + Type.String({ + description: "Poll question for the poll action.", + }) + ), + pollOption: Type.Optional( + Type.Union([Type.String(), Type.Array(Type.String())], { + description: "Poll option label or list of labels.", + }) + ), + pollMulti: Type.Optional( + Type.Boolean({ + description: "Allow multiple poll selections.", + }) + ), + pollDurationHours: Type.Optional( + Type.Integer({ + description: "Optional poll window in hours.", + }) + ), displayName: Type.Optional( Type.String({ description: "New SimpleX group display name.", diff --git a/src/channel/contacts/simplex-directory.test.ts b/src/channel/contacts/simplex-directory.test.ts new file mode 100644 index 0000000..fbda16f --- /dev/null +++ b/src/channel/contacts/simplex-directory.test.ts @@ -0,0 +1,121 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +const simplexApiMock = vi.hoisted(() => ({ + contacts: [] as unknown[], + groups: [] as unknown[], + members: [] as unknown[], + reset() { + this.contacts = []; + this.groups = []; + this.members = []; + }, +})); + +vi.mock("../../simplex/runtime/transport.js", () => ({ + withSimplexClient: async (params: { run: (client: unknown) => Promise }): Promise => { + const client = { + getActiveUser: vi.fn(async () => ({ + userId: 1, + profile: { displayName: "OpenClaw SimpleX" }, + })), + listContacts: vi.fn(async () => simplexApiMock.contacts), + listGroups: vi.fn(async () => simplexApiMock.groups), + listGroupMembers: vi.fn(async () => simplexApiMock.members), + }; + return await params.run(client); + }, +})); + +import { + listSimplexDirectoryGroups, + listSimplexDirectoryPeers, + listSimplexGroupMembers, +} from "./simplex-directory.js"; + +const cfg = { + channels: { + "openclaw-simplex": { + connection: { + wsUrl: "ws://127.0.0.1:5225", + }, + }, + }, +} as OpenClawConfig; + +const runtime = { + error: vi.fn(), +} as unknown as RuntimeEnv; + +describe("simplex directory mapping", () => { + afterEach(() => { + simplexApiMock.reset(); + vi.clearAllMocks(); + }); + + it("maps nested contact payloads and applies query filtering", async () => { + simplexApiMock.contacts = [ + { + contact: { + contactId: 7, + profile: { displayName: "Alice" }, + }, + }, + { + contact: { + contactId: 8, + localDisplayName: "Bob", + }, + }, + ]; + + await expect(listSimplexDirectoryPeers({ cfg, runtime, query: "ali" })).resolves.toEqual([ + expect.objectContaining({ + kind: "user", + id: "7", + name: "Alice", + }), + ]); + }); + + it("maps nested group payloads", async () => { + simplexApiMock.groups = [ + { + groupInfo: { + groupId: 42, + groupProfile: { displayName: "Ops" }, + }, + }, + ]; + + await expect(listSimplexDirectoryGroups({ cfg, runtime })).resolves.toEqual([ + expect.objectContaining({ + kind: "group", + id: "42", + name: "Ops", + }), + ]); + }); + + it("maps group member payloads", async () => { + simplexApiMock.members = [ + { + groupMember: { + groupMemberId: 101, + profile: { displayName: "Carol" }, + }, + }, + ]; + + await expect( + listSimplexGroupMembers({ cfg, runtime, groupId: "42", limit: 1 }) + ).resolves.toEqual([ + expect.objectContaining({ + kind: "user", + id: "101", + name: "Carol", + }), + ]); + }); +}); diff --git a/src/channel/simplex-directory.ts b/src/channel/contacts/simplex-directory.ts similarity index 68% rename from src/channel/simplex-directory.ts rename to src/channel/contacts/simplex-directory.ts index cfd3b47..cb193bf 100644 --- a/src/channel/simplex-directory.ts +++ b/src/channel/contacts/simplex-directory.ts @@ -1,16 +1,11 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/directory-runtime"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; -import { resolveSimplexAccount } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { - buildListContactsCommand, - buildListGroupMembersCommand, - buildListGroupsCommand, - buildShowActiveUserCommand, -} from "../simplex/simplex-commands.js"; -import { SimplexWsClient } from "../simplex/simplex-ws-client.js"; -import { stripSimplexPrefix } from "./simplex-common.js"; +import { resolveSimplexAccount } from "../../config/accounts.js"; +import { parseSimplexNumericId } from "../../simplex/runtime/api.js"; +import { withSimplexClient } from "../../simplex/runtime/transport.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { stripSimplexPrefix } from "../shared/simplex-common.js"; type SimplexDirectoryParams = { cfg: OpenClawConfig; @@ -47,6 +42,26 @@ function normalizeQuery(query?: string | null): string { return (query ?? "").trim().toLowerCase(); } +function applyDirectoryFilter(params: { + entries: ChannelDirectoryEntry[]; + query?: string | null; + limit?: number | null; +}): ChannelDirectoryEntry[] { + const q = normalizeQuery(params.query); + const filtered = q + ? params.entries.filter( + (entry) => + entry.id.toLowerCase().includes(q) || (entry.name?.toLowerCase().includes(q) ?? false) + ) + : params.entries; + const limit = params.limit && params.limit > 0 ? params.limit : undefined; + return limit ? filtered.slice(0, limit) : filtered; +} + +function isDirectoryEntry(entry: ChannelDirectoryEntry | null): entry is ChannelDirectoryEntry { + return entry !== null; +} + function mapContactEntry(entry: unknown): ChannelDirectoryEntry | null { if (!entry || typeof entry !== "object") { return null; @@ -159,44 +174,24 @@ function normalizeSimplexInputId(input: string): { id: string; explicit: boolean return { id: withoutPrefix, explicit: false }; } -async function withSimplexClient( - account: ResolvedSimplexAccount, - fn: (client: SimplexWsClient) => Promise -): Promise { - const client = new SimplexWsClient({ - url: account.wsUrl, - connectTimeoutMs: account.config.connection?.connectTimeoutMs, - }); - await client.connect(); - try { - return await fn(client); - } finally { - await client.close(); - } -} - async function fetchActiveUserInfo( account: ResolvedSimplexAccount, runtime: RuntimeEnv ): Promise { try { - return await withSimplexClient(account, async (client) => { - const response = await client.sendCommand(buildShowActiveUserCommand()); - const resp = response.resp as Record | undefined; - const user = - (resp?.user as Record | undefined) ?? - (resp?.activeUser as Record | undefined) ?? - (resp?.profile as Record | undefined) ?? - (resp?.userProfile as Record | undefined) ?? - resp; - if (!user || typeof user !== "object") { - return null; - } - const profile = (user.profile as Record | undefined) ?? {}; - const userId = toId(user.userId ?? user.id ?? profile.userId); - const displayName = - toId(profile.displayName) ?? toId(profile.fullName) ?? toId(user.displayName); - return { userId, displayName, raw: user }; + return await withSimplexClient({ + account, + run: async (client) => { + const user = (await client.getActiveUser()) as Record | undefined; + if (!user || typeof user !== "object") { + return null; + } + const profile = (user.profile as Record | undefined) ?? {}; + const userId = toId(user.userId ?? user.id ?? profile.userId); + const displayName = + toId(profile.displayName) ?? toId(profile.fullName) ?? toId(user.displayName); + return { userId, displayName, raw: user }; + }, }); } catch (err) { runtime.error?.(`simplex: failed to read active user: ${String(err)}`); @@ -215,25 +210,21 @@ async function listContactsLive(params: { if (!activeUserId) { return []; } - return await withSimplexClient(params.account, async (client) => { - const response = await client.sendCommand(buildListContactsCommand(activeUserId)); - const resp = response.resp as Record | undefined; - const contacts = - (resp?.contacts as unknown[]) ?? - (resp?.contactList as unknown[]) ?? - (resp?.contactsList as unknown[]) ?? - (resp?.items as unknown[]) ?? - []; - const mapped = contacts.map(mapContactEntry).filter(Boolean) as ChannelDirectoryEntry[]; - const q = normalizeQuery(params.query); - const filtered = q - ? mapped.filter( - (entry) => - entry.id.toLowerCase().includes(q) || (entry.name?.toLowerCase().includes(q) ?? false) - ) - : mapped; - const limit = params.limit && params.limit > 0 ? params.limit : undefined; - return limit ? filtered.slice(0, limit) : filtered; + const userId = parseSimplexNumericId(activeUserId); + if (userId === null) { + return []; + } + return await withSimplexClient({ + account: params.account, + run: async (client) => { + const contacts = await client.listContacts(userId); + const mapped = contacts.map(mapContactEntry).filter(isDirectoryEntry); + return applyDirectoryFilter({ + entries: mapped, + query: params.query, + limit: params.limit, + }); + }, }); } @@ -248,30 +239,21 @@ async function listGroupsLive(params: { if (!activeUserId) { return []; } - return await withSimplexClient(params.account, async (client) => { - const response = await client.sendCommand( - buildListGroupsCommand({ - userId: activeUserId, - search: params.query ?? undefined, - }) - ); - const resp = response.resp as Record | undefined; - const groups = - (resp?.groups as unknown[]) ?? - (resp?.groupList as unknown[]) ?? - (resp?.groupsList as unknown[]) ?? - (resp?.items as unknown[]) ?? - []; - const mapped = groups.map(mapGroupEntry).filter(Boolean) as ChannelDirectoryEntry[]; - const q = normalizeQuery(params.query); - const filtered = q - ? mapped.filter( - (entry) => - entry.id.toLowerCase().includes(q) || (entry.name?.toLowerCase().includes(q) ?? false) - ) - : mapped; - const limit = params.limit && params.limit > 0 ? params.limit : undefined; - return limit ? filtered.slice(0, limit) : filtered; + const userId = parseSimplexNumericId(activeUserId); + if (userId === null) { + return []; + } + return await withSimplexClient({ + account: params.account, + run: async (client) => { + const groups = await client.listGroups({ userId, search: params.query }); + const mapped = groups.map(mapGroupEntry).filter(isDirectoryEntry); + return applyDirectoryFilter({ + entries: mapped, + query: params.query, + limit: params.limit, + }); + }, }); } @@ -281,24 +263,19 @@ async function listGroupMembersLive(params: { groupId: string; limit?: number | null; }): Promise { - return await withSimplexClient(params.account, async (client) => { - const response = await client.sendCommand( - buildListGroupMembersCommand({ - groupId: params.groupId, - }) - ); - const resp = response.resp as Record | undefined; - const members = - (resp?.members as unknown[]) ?? - (resp?.groupMembers as unknown[]) ?? - (resp?.items as unknown[]) ?? - (resp?.group as Record | undefined)?.members ?? - []; - const mapped = (Array.isArray(members) ? members : []) - .map(mapMemberEntry) - .filter(Boolean) as ChannelDirectoryEntry[]; - const limit = params.limit && params.limit > 0 ? params.limit : undefined; - return limit ? mapped.slice(0, limit) : mapped; + const groupId = parseSimplexNumericId(params.groupId); + if (groupId === null) { + return []; + } + return await withSimplexClient({ + account: params.account, + run: async (client) => { + const members = await client.listGroupMembers({ groupId }); + return applyDirectoryFilter({ + entries: members.map(mapMemberEntry).filter(isDirectoryEntry), + limit: params.limit, + }); + }, }); } diff --git a/src/channel/simplex-pairing.ts b/src/channel/contacts/simplex-pairing.ts similarity index 67% rename from src/channel/simplex-pairing.ts rename to src/channel/contacts/simplex-pairing.ts index 7861c90..2868fd2 100644 --- a/src/channel/simplex-pairing.ts +++ b/src/channel/contacts/simplex-pairing.ts @@ -1,19 +1,18 @@ import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status"; -import { resolveDefaultSimplexAccountId, resolveSimplexAccount } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import type { SimplexClientRegistry } from "./simplex-client-registry.js"; +import { resolveDefaultSimplexAccountId, resolveSimplexAccount } from "../../config/accounts.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { buildAndSendSimplexMessages } from "../messaging/simplex-send.js"; import { assertSimplexOutboundAccountReady, normalizeSimplexContactRef, stripLeadingAt, stripSimplexPrefix, -} from "./simplex-common.js"; -import { buildAndSendSimplexMessages } from "./simplex-send.js"; +} from "../shared/simplex-common.js"; -export function buildSimplexPairing( - registry: SimplexClientRegistry -): NonNullable["pairing"]> { +export function buildSimplexPairing(): NonNullable< + ChannelPlugin["pairing"] +> { return { idLabel: "simplexContactId", normalizeAllowEntry: (entry) => stripLeadingAt(stripSimplexPrefix(entry)), @@ -22,7 +21,6 @@ export function buildSimplexPairing( const account = resolveSimplexAccount({ cfg, accountId }); assertSimplexOutboundAccountReady(account); await buildAndSendSimplexMessages({ - registry, cfg, account, chatRef: normalizeSimplexContactRef(id), diff --git a/src/channel/simplex-doctor.test.ts b/src/channel/diagnostics/simplex-doctor.test.ts similarity index 69% rename from src/channel/simplex-doctor.test.ts rename to src/channel/diagnostics/simplex-doctor.test.ts index 32fa5c3..00cd5e0 100644 --- a/src/channel/simplex-doctor.test.ts +++ b/src/channel/diagnostics/simplex-doctor.test.ts @@ -11,27 +11,17 @@ describe("simplex doctor", () => { }); }); - it("warns about legacy config, invalid websocket URLs, and empty allowlists", async () => { + it("warns about legacy config and empty allowlists", async () => { const cfg = { channels: { - simplex: { - connection: { - wsUrl: "ws://127.0.0.1:5225", - }, - }, + simplex: {}, "openclaw-simplex": { - connection: { - wsUrl: "http://127.0.0.1:5225", - }, dmPolicy: "allowlist", allowFrom: [], groupPolicy: "allowlist", groupAllowFrom: [], accounts: { alt: { - connection: { - wsUrl: "tcp://127.0.0.1:5225", - }, dmPolicy: "allowlist", allowFrom: [], groupPolicy: "allowlist", @@ -48,11 +38,7 @@ describe("simplex doctor", () => { }) ?? []); expect(warnings.join("\n")).toContain("Legacy channels.simplex config is present"); - expect(warnings.join("\n")).toContain('connection.wsUrl="http://127.0.0.1:5225"'); expect(warnings.join("\n")).toContain('dmPolicy="allowlist" is configured with an empty'); expect(warnings.join("\n")).toContain('groupPolicy="allowlist" is configured with an empty'); - expect(warnings.join("\n")).toContain( - 'account "alt" has connection.wsUrl="tcp://127.0.0.1:5225"' - ); }); }); diff --git a/src/channel/simplex-doctor.ts b/src/channel/diagnostics/simplex-doctor.ts similarity index 80% rename from src/channel/simplex-doctor.ts rename to src/channel/diagnostics/simplex-doctor.ts index d878f44..bdc8bb7 100644 --- a/src/channel/simplex-doctor.ts +++ b/src/channel/diagnostics/simplex-doctor.ts @@ -1,6 +1,6 @@ import type { ChannelDoctorAdapter } from "openclaw/plugin-sdk/channel-contract"; -import type { SimplexAccountConfig, SimplexChannelConfig } from "../config/config-schema.js"; -import { LEGACY_SIMPLEX_CHANNEL_ID, SIMPLEX_CHANNEL_ID } from "../constants.js"; +import type { SimplexAccountConfig, SimplexChannelConfig } from "../../config/config-schema.js"; +import { LEGACY_SIMPLEX_CHANNEL_ID, SIMPLEX_CHANNEL_ID } from "../../constants.js"; function isEmptyArray(value: unknown): boolean { return Array.isArray(value) && value.length === 0; @@ -13,16 +13,9 @@ function readChannelConfig(cfg: { channels?: Record }): Simplex function collectAccountWarnings( channel: SimplexChannelConfig, accountId: string, - account: SimplexAccountConfig, - doctorFixCommand: string + account: SimplexAccountConfig ): string[] { const warnings: string[] = []; - const wsUrl = account.connection?.wsUrl ?? ""; - if (wsUrl && !/^wss?:\/\//i.test(wsUrl)) { - warnings.push( - `- SimpleX account "${accountId}" has connection.wsUrl="${wsUrl}". Use ws:// or wss://, then run ${doctorFixCommand}.` - ); - } const inheritedAllowFrom = account.allowFrom ?? channel.allowFrom; if (account.dmPolicy === "allowlist" && isEmptyArray(inheritedAllowFrom)) { @@ -54,11 +47,6 @@ export const simplexDoctor: ChannelDoctorAdapter = { } const channel = readChannelConfig(cfg); - const baseWsUrl = typeof channel.connection?.wsUrl === "string" ? channel.connection.wsUrl : ""; - if (baseWsUrl && !/^wss?:\/\//i.test(baseWsUrl)) { - warnings.push(`- SimpleX connection.wsUrl="${baseWsUrl}" must start with ws:// or wss://.`); - } - if (channel.dmPolicy === "allowlist" && isEmptyArray(channel.allowFrom)) { warnings.push( '- SimpleX dmPolicy="allowlist" is configured with an empty allowFrom list. New contacts will be dropped until allowFrom is populated.' @@ -75,7 +63,7 @@ export const simplexDoctor: ChannelDoctorAdapter = { if (!account) { continue; } - warnings.push(...collectAccountWarnings(channel, accountId, account, doctorFixCommand)); + warnings.push(...collectAccountWarnings(channel, accountId, account)); } return warnings; diff --git a/src/channel/diagnostics/simplex-status.test.ts b/src/channel/diagnostics/simplex-status.test.ts new file mode 100644 index 0000000..417869d --- /dev/null +++ b/src/channel/diagnostics/simplex-status.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { simplexPlugin } from "../plugin.js"; +import { buildSimplexStatus } from "./simplex-status.js"; + +function account(overrides: Partial = {}): ResolvedSimplexAccount { + return { + accountId: "default", + enabled: true, + configured: true, + mode: "external", + wsUrl: "ws://127.0.0.1:5225", + wsHost: "127.0.0.1", + wsPort: 5225, + config: { + connection: { + wsHost: "127.0.0.1", + wsPort: 5225, + }, + }, + ...overrides, + }; +} + +describe("simplex status adapter", () => { + it("falls back to resolved account configuration for channel summaries", async () => { + const summary = await buildSimplexStatus().buildChannelSummary?.({ + account: account(), + cfg: { channels: {} }, + defaultAccountId: "default", + snapshot: { + accountId: "default", + running: false, + connected: false, + }, + }); + + expect(summary).toMatchObject({ + configured: true, + running: false, + connected: false, + healthState: "ready", + mode: "external", + wsUrl: "ws://127.0.0.1:5225", + }); + }); + + it("does not expose raw account config through inspectAccount", () => { + expect(simplexPlugin.config.inspectAccount).toBeUndefined(); + }); +}); diff --git a/src/channel/diagnostics/simplex-status.ts b/src/channel/diagnostics/simplex-status.ts new file mode 100644 index 0000000..1feb559 --- /dev/null +++ b/src/channel/diagnostics/simplex-status.ts @@ -0,0 +1,118 @@ +import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id"; +import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import { describeSimplexWsEndpointSecurity } from "../../simplex/runtime/security.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { resolveSimplexHealthState } from "../shared/simplex-common.js"; + +export function buildSimplexStatus(): NonNullable["status"]> { + return { + defaultRuntime: { + accountId: DEFAULT_ACCOUNT_ID, + running: false, + lastStartAt: null, + lastStopAt: null, + lastError: null, + }, + collectStatusIssues: (accounts) => + accounts.flatMap((account) => { + const issues = []; + const lastError = typeof account.lastError === "string" ? account.lastError.trim() : ""; + if (lastError) { + issues.push({ + channel: SIMPLEX_CHANNEL_ID, + accountId: account.accountId, + kind: "runtime" as const, + message: `Channel error: ${lastError}`, + }); + } + + const transportWarnings = ( + account.application as { transportWarnings?: unknown } | undefined + )?.transportWarnings; + if (Array.isArray(transportWarnings)) { + for (const warning of transportWarnings) { + if (typeof warning === "string" && warning.trim()) { + issues.push({ + channel: SIMPLEX_CHANNEL_ID, + accountId: account.accountId, + kind: "runtime" as const, + message: `Transport warning: ${warning}`, + }); + } + } + } + return issues; + }), + buildChannelSummary: ({ snapshot, account }) => { + const connection = account.config.connection; + const configured = snapshot.configured ?? account.configured; + const running = snapshot.running ?? false; + const connected = snapshot.connected ?? false; + const lastError = snapshot.lastError ?? null; + const security = describeSimplexWsEndpointSecurity(account.wsUrl, { + allowUnsafeRemoteWs: connection?.allowUnsafeRemoteWs, + }); + return { + configured, + running, + connected, + lastStartAt: snapshot.lastStartAt ?? null, + lastStopAt: snapshot.lastStopAt ?? null, + lastError, + lastConnectedAt: snapshot.lastConnectedAt ?? null, + lastDisconnect: snapshot.lastDisconnect ?? null, + lastEventAt: snapshot.lastEventAt ?? null, + healthState: + security.blockingWarnings.length > 0 + ? "error" + : resolveSimplexHealthState({ + configured, + running, + connected, + lastError, + }), + mode: snapshot.mode ?? account.mode, + wsUrl: account.wsUrl, + transportWarnings: security.warnings, + }; + }, + buildAccountSnapshot: async ({ account, runtime }) => { + const connection = account.config.connection; + const security = describeSimplexWsEndpointSecurity(account.wsUrl, { + allowUnsafeRemoteWs: connection?.allowUnsafeRemoteWs, + }); + return { + accountId: account.accountId, + name: account.name, + enabled: account.enabled, + configured: account.configured, + running: runtime?.running ?? false, + connected: runtime?.connected ?? false, + lastStartAt: runtime?.lastStartAt ?? null, + lastStopAt: runtime?.lastStopAt ?? null, + lastConnectedAt: runtime?.lastConnectedAt ?? null, + lastDisconnect: runtime?.lastDisconnect ?? null, + lastError: runtime?.lastError ?? null, + healthState: + security.blockingWarnings.length > 0 + ? "error" + : resolveSimplexHealthState({ + configured: account.configured, + running: runtime?.running ?? false, + connected: runtime?.connected ?? false, + lastError: runtime?.lastError ?? null, + }), + mode: runtime?.mode ?? account.mode, + lastInboundAt: runtime?.lastInboundAt ?? null, + lastOutboundAt: runtime?.lastOutboundAt ?? null, + lastEventAt: runtime?.lastEventAt ?? null, + application: { + wsUrl: account.wsUrl, + transportWarnings: security.warnings, + transportBlocked: security.blockingWarnings.length > 0, + }, + }; + }, + }; +} diff --git a/src/channel/events/simplex-event-parser.ts b/src/channel/events/simplex-event-parser.ts new file mode 100644 index 0000000..0abd4de --- /dev/null +++ b/src/channel/events/simplex-event-parser.ts @@ -0,0 +1,113 @@ +import type { SimplexChatContext, SimplexChatItem } from "../../types/events.js"; +import { stripSimplexPrefix } from "../shared/simplex-common.js"; + +const INBOUND_DIRS = new Set(["directRcv", "groupRcv"]); + +export function normalizeSimplexSenderId(value?: string | null): string | undefined { + let trimmed = value?.trim(); + if (!trimmed) { + return undefined; + } + trimmed = stripSimplexPrefix(trimmed); + if (!trimmed) { + return undefined; + } + if (trimmed.startsWith("@")) { + trimmed = trimmed.slice(1).trim(); + } else { + const kindLower = trimmed.toLowerCase(); + if (kindLower.startsWith("contact:")) { + trimmed = trimmed.slice("contact:".length).trim(); + } else if (kindLower.startsWith("user:")) { + trimmed = trimmed.slice("user:".length).trim(); + } else if (kindLower.startsWith("member:")) { + trimmed = trimmed.slice("member:".length).trim(); + } + } + return trimmed || undefined; +} + +export function resolveSimplexMessageText( + content: { type?: string; text?: string } | undefined, + fileName?: string +): string { + if (!content) { + return ""; + } + const text = content.text?.trim() ?? ""; + if (text) { + return text; + } + switch (content.type) { + case "image": + return "[image]"; + case "video": + return "[video]"; + case "voice": + return "[voice message]"; + case "file": + return fileName ? `[file: ${fileName}]` : "[file]"; + case "link": + return "[link]"; + case "report": + return "[report]"; + case "chat": + return "[chat]"; + default: + return "[message]"; + } +} + +export function isInboundSimplexChatItem(item: SimplexChatItem): boolean { + const dir = item.chatItem?.chatDir?.type; + return Boolean(dir && INBOUND_DIRS.has(dir)); +} + +export function resolveSimplexChatContext(item: SimplexChatItem): SimplexChatContext | null { + const info = item.chatInfo; + if (!info || !item.chatItem) { + return null; + } + if (info.type === "direct") { + const contactId = info.contact?.contactId; + if (typeof contactId !== "number") { + return null; + } + const senderName = + info.contact?.localDisplayName?.trim() || + info.contact?.profile?.displayName?.trim() || + undefined; + return { + chatType: "direct", + chatId: contactId, + chatLabel: senderName || `contact:${contactId}`, + senderId: String(contactId), + senderName, + }; + } + if (info.type === "group") { + const groupId = info.groupInfo?.groupId; + if (typeof groupId !== "number") { + return null; + } + const member = item.chatItem?.chatDir?.groupMember; + const contactId = + typeof member?.contactId === "number" + ? String(member.contactId) + : member?.contactId?.trim() || undefined; + const senderId = + contactId ?? + member?.memberId?.trim() ?? + (typeof member?.groupMemberId === "number" ? String(member.groupMemberId) : undefined); + const senderName = member?.localDisplayName?.trim() || undefined; + const groupLabel = info.groupInfo?.localDisplayName?.trim() || `group:${groupId}`; + return { + chatType: "group", + chatId: groupId, + chatLabel: groupLabel, + senderId: senderId || undefined, + senderName, + }; + } + return null; +} diff --git a/src/channel/events/simplex-inbound-auth.test.ts b/src/channel/events/simplex-inbound-auth.test.ts new file mode 100644 index 0000000..3310217 --- /dev/null +++ b/src/channel/events/simplex-inbound-auth.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it, vi } from "vitest"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexChatContext } from "../../types/events.js"; +import { resolveSimplexInboundAccess } from "./simplex-inbound-auth.js"; + +function account(config: ResolvedSimplexAccount["config"] = {}): ResolvedSimplexAccount { + return { + accountId: "default", + enabled: true, + configured: true, + mode: "external", + wsUrl: "ws://127.0.0.1:5225", + wsHost: "127.0.0.1", + wsPort: 5225, + config, + }; +} + +function directContext(): SimplexChatContext { + return { + chatType: "direct", + chatId: 42, + chatLabel: "Alice", + senderId: "42", + senderName: "Alice", + }; +} + +function groupContext(): SimplexChatContext { + return { + chatType: "group", + chatId: 7, + chatLabel: "Ops", + senderId: "42", + senderName: "Alice", + }; +} + +function runtimeCore( + params: { + storeAllowFrom?: string[]; + shouldComputeAuth?: boolean; + commandAuthorized?: boolean; + mentioned?: boolean; + controlCommand?: boolean; + } = {} +) { + return { + channel: { + commands: { + shouldComputeCommandAuthorized: vi.fn(() => params.shouldComputeAuth ?? false), + resolveCommandAuthorizedFromAuthorizers: vi.fn(() => params.commandAuthorized ?? false), + shouldHandleTextCommands: vi.fn(() => true), + isControlCommandMessage: vi.fn(() => params.controlCommand ?? false), + }, + pairing: { + readAllowFromStore: vi.fn(async () => params.storeAllowFrom ?? []), + upsertPairingRequest: vi.fn(async () => ({ code: "123456", created: true })), + buildPairingReply: vi.fn(() => "pairing reply"), + }, + mentions: { + buildMentionRegexes: vi.fn(() => [/agent/i]), + matchesMentionPatterns: vi.fn(() => params.mentioned ?? false), + }, + text: { + hasControlCommand: vi.fn(() => params.controlCommand ?? false), + }, + }, + }; +} + +const runtime = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), +} as never; + +describe("resolveSimplexInboundAccess", () => { + it("creates a pairing request and rejects unknown DMs in pairing mode", async () => { + const core = runtimeCore(); + const replyToPairingRequest = vi.fn(async () => undefined); + + const result = await resolveSimplexInboundAccess({ + account: account({ dmPolicy: "pairing" }), + cfg: {}, + runtime, + core: core as never, + context: directContext(), + rawBody: "hello", + normalizedSenderId: "42", + routeAgentId: "agent", + replyToPairingRequest, + }); + + expect(result.allowed).toBe(false); + expect(core.channel.pairing.upsertPairingRequest).toHaveBeenCalledWith( + expect.objectContaining({ id: "42", accountId: "default" }) + ); + expect(replyToPairingRequest).toHaveBeenCalledWith("pairing reply"); + }); + + it("allows allowlisted group messages when mention policy is satisfied", async () => { + const result = await resolveSimplexInboundAccess({ + account: account({ groupPolicy: "allowlist", groupAllowFrom: ["group:7"] }), + cfg: {}, + runtime, + core: runtimeCore({ mentioned: true }) as never, + context: groupContext(), + rawBody: "@agent status", + normalizedSenderId: "42", + routeAgentId: "agent", + replyToPairingRequest: vi.fn(), + }); + + expect(result).toMatchObject({ + allowed: true, + effectiveWasMentioned: true, + }); + }); + + it("rejects unauthorized group control commands", async () => { + const result = await resolveSimplexInboundAccess({ + account: account({ groupPolicy: "open" }), + cfg: { commands: { useAccessGroups: true } }, + runtime, + core: runtimeCore({ + shouldComputeAuth: true, + commandAuthorized: false, + mentioned: true, + controlCommand: true, + }) as never, + context: groupContext(), + rawBody: "!approve", + normalizedSenderId: "42", + routeAgentId: "agent", + replyToPairingRequest: vi.fn(), + }); + + expect(result.allowed).toBe(false); + expect(result.commandAuthorized).toBe(false); + }); +}); diff --git a/src/channel/events/simplex-inbound-auth.ts b/src/channel/events/simplex-inbound-auth.ts new file mode 100644 index 0000000..1fe2642 --- /dev/null +++ b/src/channel/events/simplex-inbound-auth.ts @@ -0,0 +1,196 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexChatContext } from "../../types/events.js"; +import type { getSimplexRuntime } from "../runtime.js"; +import { isSimplexAllowlisted } from "../security/simplex-security.js"; + +type SimplexRuntimeCore = ReturnType; + +export type SimplexInboundAccessResult = { + allowed: boolean; + commandAuthorized?: boolean; + effectiveWasMentioned?: boolean; +}; + +function resolveSimplexGroupRequireMention(params: { + account: ResolvedSimplexAccount; + groupId?: number | null; +}): boolean { + const groupId = params.groupId ? String(params.groupId) : undefined; + const groups = params.account.config.groups ?? {}; + const entry = groupId ? groups[groupId] : undefined; + const fallback = groups["*"]; + if (typeof entry?.requireMention === "boolean") { + return entry.requireMention; + } + if (typeof fallback?.requireMention === "boolean") { + return fallback.requireMention; + } + return true; +} + +export async function resolveSimplexInboundAccess(params: { + account: ResolvedSimplexAccount; + cfg: OpenClawConfig; + runtime: RuntimeEnv; + core: SimplexRuntimeCore; + context: SimplexChatContext; + rawBody: string; + normalizedSenderId?: string; + routeAgentId: string; + replyToPairingRequest: (text: string) => Promise; +}): Promise { + const { account, cfg, runtime, core, context, rawBody, normalizedSenderId } = params; + const isGroup = context.chatType === "group"; + const dmPolicy = account.config.dmPolicy ?? "pairing"; + const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy; + const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist"; + const configAllowFrom = (account.config.allowFrom ?? []).map((entry) => String(entry)); + const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((entry) => String(entry)); + const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, cfg); + const shouldLoadAllowFromStore = + (!isGroup && (dmPolicy !== "open" || shouldComputeAuth)) || + (isGroup && (groupPolicy !== "open" || shouldComputeAuth)); + const storeAllowFrom = shouldLoadAllowFromStore + ? await core.channel.pairing + .readAllowFromStore({ + channel: SIMPLEX_CHANNEL_ID, + accountId: account.accountId, + }) + .catch(() => []) + : []; + const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom]; + const baseGroupAllowFrom = + configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom; + const effectiveGroupAllowFrom = [...baseGroupAllowFrom, ...storeAllowFrom]; + const allowlistForCommands = isGroup ? effectiveGroupAllowFrom : effectiveDmAllowFrom; + const senderAllowedForCommands = isSimplexAllowlisted({ + allowFrom: allowlistForCommands, + senderId: normalizedSenderId, + groupId: String(context.chatId), + allowGroupId: isGroup, + }); + const useAccessGroups = cfg.commands?.useAccessGroups !== false; + const commandAuthorized = shouldComputeAuth + ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({ + useAccessGroups, + authorizers: [ + { + configured: allowlistForCommands.length > 0, + allowed: senderAllowedForCommands, + }, + ], + }) + : undefined; + + if (isGroup) { + if (groupPolicy === "disabled") { + runtime.log?.(`[${account.accountId}] SimpleX drop group (groupPolicy=disabled)`); + return { allowed: false, commandAuthorized }; + } + if (groupPolicy === "allowlist") { + if (effectiveGroupAllowFrom.length === 0) { + runtime.log?.( + `[${account.accountId}] SimpleX drop group (groupPolicy=allowlist, empty allowlist)` + ); + return { allowed: false, commandAuthorized }; + } + const allowed = isSimplexAllowlisted({ + allowFrom: effectiveGroupAllowFrom, + senderId: normalizedSenderId, + groupId: String(context.chatId), + allowGroupId: true, + }); + if (!allowed) { + runtime.log?.( + `[${account.accountId}] SimpleX drop group sender ${context.senderId ?? "unknown"} (not allowlisted)` + ); + return { allowed: false, commandAuthorized }; + } + } + } else if (dmPolicy !== "open") { + const allowed = isSimplexAllowlisted({ + allowFrom: effectiveDmAllowFrom, + senderId: normalizedSenderId, + allowGroupId: false, + }); + if (!allowed) { + if (dmPolicy === "pairing") { + const senderId = normalizedSenderId ?? String(context.chatId); + const { code, created } = await core.channel.pairing.upsertPairingRequest({ + channel: SIMPLEX_CHANNEL_ID, + id: senderId, + accountId: account.accountId, + meta: { name: context.senderName }, + }); + if (created) { + runtime.log?.(`[${account.accountId}] SimpleX pairing request sender=${senderId}`); + try { + await params.replyToPairingRequest( + core.channel.pairing.buildPairingReply({ + channel: SIMPLEX_CHANNEL_ID, + idLine: `Your SimpleX contact id: ${senderId}`, + code, + }) + ); + } catch (err) { + runtime.error?.(`[${account.accountId}] SimpleX pairing reply failed: ${String(err)}`); + } + } + } else { + runtime.log?.( + `[${account.accountId}] SimpleX drop DM from ${context.senderId ?? "unknown"} (dmPolicy=${dmPolicy})` + ); + } + return { allowed: false, commandAuthorized }; + } + } + + let effectiveWasMentioned: boolean | undefined; + if (isGroup) { + const requireMention = resolveSimplexGroupRequireMention({ + account, + groupId: context.chatId, + }); + const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, params.routeAgentId); + const wasMentioned = core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes); + const allowTextCommands = core.channel.commands.shouldHandleTextCommands({ + cfg, + surface: SIMPLEX_CHANNEL_ID, + }); + const mentionGate = resolveInboundMentionDecision({ + facts: { + canDetectMention: mentionRegexes.length > 0, + wasMentioned, + }, + policy: { + isGroup: true, + requireMention, + allowTextCommands, + hasControlCommand: core.channel.text.hasControlCommand(rawBody, cfg), + commandAuthorized: commandAuthorized === true, + }, + }); + effectiveWasMentioned = mentionGate.effectiveWasMentioned; + if (mentionGate.shouldSkip) { + runtime.log?.( + `[${account.accountId}] SimpleX drop group ${context.chatId} (mention required)` + ); + return { allowed: false, commandAuthorized, effectiveWasMentioned }; + } + } + + if (isGroup && core.channel.commands.isControlCommandMessage(rawBody, cfg)) { + if (commandAuthorized !== true) { + runtime.log?.( + `[${account.accountId}] SimpleX drop control command from ${context.senderId ?? "unknown"}` + ); + return { allowed: false, commandAuthorized, effectiveWasMentioned }; + } + } + + return { allowed: true, commandAuthorized, effectiveWasMentioned }; +} diff --git a/src/channel/events/simplex-inbound-context.test.ts b/src/channel/events/simplex-inbound-context.test.ts new file mode 100644 index 0000000..623183c --- /dev/null +++ b/src/channel/events/simplex-inbound-context.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it, vi } from "vitest"; +import type { SimplexChatContext } from "../../types/events.js"; +import { buildSimplexInboundDispatchContext } from "./simplex-inbound-context.js"; + +function groupContext(): SimplexChatContext { + return { + chatType: "group", + chatId: 7, + chatLabel: "Ops", + senderId: "42", + senderName: "Alice", + }; +} + +function runtimeCore() { + return { + channel: { + session: { + resolveStorePath: vi.fn(() => "/tmp/session-store"), + readSessionUpdatedAt: vi.fn(() => 123), + }, + reply: { + resolveEnvelopeFormatOptions: vi.fn(() => ({ enabled: true })), + formatAgentEnvelope: vi.fn(({ body }) => `wrapped:${body}`), + finalizeInboundContext: vi.fn((ctx) => ctx), + }, + }, + }; +} + +describe("buildSimplexInboundDispatchContext", () => { + it("builds a group inbound context with route and authorization metadata", () => { + const core = runtimeCore(); + + const result = buildSimplexInboundDispatchContext({ + core: core as never, + cfg: {}, + context: groupContext(), + route: { + agentId: "agent", + accountId: "default", + sessionKey: "simplex:group:7", + }, + rawBody: "hello", + dmPeerId: "42", + currentMessageId: 99, + effectiveWasMentioned: true, + commandAuthorized: true, + }); + + expect(result.storePath).toBe("/tmp/session-store"); + expect(result.sessionKey).toBe("simplex:group:7"); + expect(result.ctxPayload).toMatchObject({ + Body: "wrapped:hello", + RawBody: "hello", + From: "openclaw-simplex:group:7", + To: "openclaw-simplex:group:7", + AccountId: "default", + ChatType: "group", + ConversationLabel: "group:7", + GroupSubject: "Ops", + MessageSid: "99", + CurrentMessageId: "99", + WasMentioned: true, + CommandAuthorized: true, + OriginatingTo: "openclaw-simplex:group:7", + }); + expect(core.channel.session.resolveStorePath).toHaveBeenCalledWith(undefined, { + agentId: "agent", + }); + }); +}); diff --git a/src/channel/events/simplex-inbound-context.ts b/src/channel/events/simplex-inbound-context.ts new file mode 100644 index 0000000..1dbf85e --- /dev/null +++ b/src/channel/events/simplex-inbound-context.ts @@ -0,0 +1,96 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import type { SimplexChatContext } from "../../types/events.js"; +import type { getSimplexRuntime } from "../runtime.js"; + +type SimplexRuntimeCore = ReturnType; + +type SimplexAgentRoute = { + agentId: string; + sessionKey: string; + accountId: string; +}; + +export function buildSimplexInboundDispatchContext(params: { + core: SimplexRuntimeCore; + cfg: OpenClawConfig; + context: SimplexChatContext; + route: SimplexAgentRoute; + rawBody: string; + dmPeerId: string; + currentMessageId?: number; + effectiveWasMentioned?: boolean; + commandAuthorized?: boolean; +}): { + ctxPayload: Record; + storePath: string; + sessionKey: string; +} { + const { + core, + cfg, + context, + route, + rawBody, + dmPeerId, + currentMessageId, + effectiveWasMentioned, + commandAuthorized, + } = params; + const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { + agentId: route.agentId, + }); + + const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg); + const previousTimestamp = core.channel.session.readSessionUpdatedAt({ + storePath, + sessionKey: route.sessionKey, + }); + + const fromLabel = + context.chatType === "group" + ? `group:${context.chatId}` + : context.senderName || `contact:${context.senderId ?? "unknown"}`; + + const body = core.channel.reply.formatAgentEnvelope({ + channel: "SimpleX", + from: fromLabel, + previousTimestamp, + envelope: envelopeOptions, + body: rawBody, + }); + + const conversationId = + context.chatType === "group" + ? `${SIMPLEX_CHANNEL_ID}:group:${context.chatId}` + : `${SIMPLEX_CHANNEL_ID}:${dmPeerId}`; + + const ctxPayload = core.channel.reply.finalizeInboundContext({ + Body: body, + RawBody: rawBody, + CommandBody: rawBody, + From: conversationId, + To: conversationId, + SessionKey: route.sessionKey, + AccountId: route.accountId, + ChatType: context.chatType === "group" ? "group" : "direct", + ConversationLabel: fromLabel, + GroupSubject: context.chatType === "group" ? context.chatLabel : undefined, + SenderName: context.senderName, + SenderId: context.senderId, + Provider: SIMPLEX_CHANNEL_ID, + Surface: SIMPLEX_CHANNEL_ID, + MessageSid: currentMessageId !== undefined ? String(currentMessageId) : undefined, + CurrentMessageId: currentMessageId !== undefined ? String(currentMessageId) : undefined, + WasMentioned: context.chatType === "group" ? effectiveWasMentioned : undefined, + CommandAuthorized: commandAuthorized, + OriginatingChannel: SIMPLEX_CHANNEL_ID, + OriginatingTo: conversationId, + }); + + return { + ctxPayload, + storePath, + sessionKey: route.sessionKey, + }; +} diff --git a/src/channel/events/simplex-inbound-files.ts b/src/channel/events/simplex-inbound-files.ts new file mode 100644 index 0000000..444f8c4 --- /dev/null +++ b/src/channel/events/simplex-inbound-files.ts @@ -0,0 +1,164 @@ +import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import type { SimplexClient } from "../../simplex/runtime/client.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { getSimplexRuntime } from "../runtime.js"; + +const PENDING_FILE_TIMEOUT_MS = 90_000; + +type SimplexReplyPayload = { + text?: string; + mediaUrl?: string; + mediaUrls?: string[]; + audioAsVoice?: boolean; + replyToId?: string | number | null; +}; + +export type PendingInboundFile = { + fileId: number; + ctxPayload: Record; + storePath: string; + sessionKey: string; + account: ResolvedSimplexAccount; + cfg: OpenClawConfig; + runtime: RuntimeEnv; + client: SimplexClient; + sendPayload: (payload: SimplexReplyPayload) => Promise; + statusSink?: (patch: Partial) => void; +}; + +const pendingFiles = new Map(); + +function pendingKey(accountId: string, fileId: number): string { + return `${accountId}:${fileId}`; +} + +export async function requestFileDownload(params: { + fileId: number; + account: ResolvedSimplexAccount; + client: SimplexClient; + runtime: RuntimeEnv; +}): Promise { + const { fileId, account, client, runtime } = params; + const autoAccept = account.config.connection?.autoAcceptFiles !== false; + if (!autoAccept) { + return false; + } + try { + await client.receiveFile(fileId); + } catch (err) { + runtime.error?.(`[${account.accountId}] SimpleX receive file failed: ${String(err)}`); + return false; + } + return true; +} + +export function queuePendingFile(params: { + pending: PendingInboundFile; + accountId: string; + fileId: number; +}): void { + const { pending, accountId, fileId } = params; + pendingFiles.set(pendingKey(accountId, fileId), pending); + setTimeout(() => { + const key = pendingKey(accountId, fileId); + const current = pendingFiles.get(key); + if (!current) { + return; + } + pendingFiles.delete(key); + void current.client.cancelFile(fileId).catch((err) => { + current.runtime.error?.(`[${accountId}] SimpleX file timeout cancel failed: ${String(err)}`); + }); + void dispatchInbound({ + pending: current, + mediaPath: undefined, + mediaType: undefined, + }).catch((err) => { + current.runtime.error?.(`[${accountId}] SimpleX pending file timeout: ${String(err)}`); + }); + }, PENDING_FILE_TIMEOUT_MS); +} + +export async function finalizePendingFile(params: { + accountId: string; + fileId: number; + filePath?: string; +}): Promise { + const pending = pendingFiles.get(pendingKey(params.accountId, params.fileId)); + if (!pending) { + return; + } + pendingFiles.delete(pendingKey(params.accountId, params.fileId)); + const mediaPath = params.filePath?.trim() || undefined; + let mediaType: string | undefined; + if (mediaPath) { + mediaType = await getSimplexRuntime().media.detectMime({ filePath: mediaPath }); + } + await dispatchInbound({ pending, mediaPath, mediaType }); +} + +export function hasPendingFile(accountId: string, fileId: number): boolean { + return pendingFiles.has(pendingKey(accountId, fileId)); +} + +export async function dispatchInbound(params: { + pending: PendingInboundFile; + mediaPath?: string; + mediaType?: string; +}): Promise { + const { pending, mediaPath, mediaType } = params; + const core = getSimplexRuntime(); + const ctxPayload = { + ...pending.ctxPayload, + MediaPath: mediaPath, + MediaType: mediaType, + MediaUrl: mediaPath, + }; + + await core.channel.session.recordInboundSession({ + storePath: pending.storePath, + sessionKey: (ctxPayload as { SessionKey?: string }).SessionKey ?? pending.sessionKey, + ctx: ctxPayload, + onRecordError: (err) => { + pending.runtime.error?.(`simplex: failed updating session meta: ${String(err)}`); + }, + }); + + pending.statusSink?.({ lastInboundAt: Date.now() }); + + await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({ + ctx: ctxPayload, + cfg: pending.cfg, + dispatcherOptions: { + deliver: async (payload) => { + const hasMedia = + Boolean(payload.mediaUrl) || + (Array.isArray(payload.mediaUrls) && payload.mediaUrls.length > 0); + if (!payload.text && !hasMedia) { + return; + } + if (!pending.account.enabled || !pending.account.configured) { + pending.runtime.error?.( + `[${pending.account.accountId}] SimpleX reply skipped: account not ready (enabled=${pending.account.enabled}, configured=${pending.account.configured})` + ); + return; + } + await pending.sendPayload(payload); + pending.statusSink?.({ lastOutboundAt: Date.now() }); + }, + onError: (err) => { + pending.runtime.error?.( + `[${pending.account.accountId}] SimpleX reply failed: ${String(err)}` + ); + }, + }, + replyOptions: { + disableBlockStreaming: + typeof pending.account.config.blockStreaming === "boolean" + ? !pending.account.config.blockStreaming + : undefined, + }, + }); +} diff --git a/src/channel/events/simplex-monitor.test.ts b/src/channel/events/simplex-monitor.test.ts new file mode 100644 index 0000000..70bface --- /dev/null +++ b/src/channel/events/simplex-monitor.test.ts @@ -0,0 +1,137 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import { describe, expect, it, vi } from "vitest"; +import type { SimplexConnectionState } from "../../simplex/runtime/ws-client.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; + +const clientMock = vi.hoisted(() => ({ + instances: [] as Array<{ + connect: ReturnType; + close: ReturnType; + emitConnectionState: (state: SimplexConnectionState) => void; + eventListenersRegisteredBeforeConnect: () => boolean; + }>, + reset() { + this.instances.length = 0; + }, +})); + +vi.mock("../../simplex/runtime/client.js", () => ({ + SimplexClient: class { + private connectionHandlers = new Set<(state: SimplexConnectionState) => void>(); + private eventHandlers = new Set<(event: unknown) => void>(); + private hadEventListenersWhenConnectStarted = false; + readonly connect = vi.fn(async () => { + this.hadEventListenersWhenConnectStarted = this.eventHandlers.size > 0; + }); + readonly close = vi.fn(async () => undefined); + + constructor() { + clientMock.instances.push({ + connect: this.connect, + close: this.close, + eventListenersRegisteredBeforeConnect: () => this.hadEventListenersWhenConnectStarted, + emitConnectionState: (state) => { + for (const handler of this.connectionHandlers) { + handler(state); + } + }, + }); + } + + onConnectionState(handler: (state: SimplexConnectionState) => void): () => void { + this.connectionHandlers.add(handler); + return () => this.connectionHandlers.delete(handler); + } + + onEvent(): () => void { + const handler = () => undefined; + this.eventHandlers.add(handler); + return () => this.eventHandlers.delete(handler); + } + }, +})); + +import { startSimplexMonitor } from "./simplex-monitor.js"; + +function account(): ResolvedSimplexAccount { + return { + accountId: "default", + enabled: true, + configured: true, + mode: "external", + wsUrl: "ws://127.0.0.1:5225", + wsHost: "127.0.0.1", + wsPort: 5225, + config: { + allowFrom: [], + groupAllowFrom: [], + connection: { + wsUrl: "ws://127.0.0.1:5225", + }, + }, + }; +} + +describe("simplex monitor connection lifecycle", () => { + it("reconnects after an unexpected websocket disconnect", async () => { + clientMock.reset(); + const statuses: unknown[] = []; + + await startSimplexMonitor({ + account: account(), + cfg: { channels: {} } as OpenClawConfig, + runtime: { + log() {}, + error() {}, + exit() { + throw new Error("unexpected exit"); + }, + } as RuntimeEnv, + abortSignal: new AbortController().signal, + statusSink: (status) => statuses.push(status), + }); + + const client = clientMock.instances[0]; + expect(client?.connect).toHaveBeenCalledTimes(1); + + client?.emitConnectionState({ + connected: false, + at: 123, + expected: false, + error: "runtime closed", + }); + + await vi.waitFor(() => expect(client?.connect).toHaveBeenCalledTimes(2)); + expect(statuses).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + connected: false, + running: true, + healthState: "starting", + }), + ]) + ); + }); + + it("subscribes for runtime events before opening the websocket", async () => { + clientMock.reset(); + + await startSimplexMonitor({ + account: account(), + cfg: { channels: {} } as OpenClawConfig, + runtime: { + log() {}, + error() {}, + exit() { + throw new Error("unexpected exit"); + }, + } as RuntimeEnv, + abortSignal: new AbortController().signal, + }); + + const client = clientMock.instances[0]; + expect(client?.connect).toHaveBeenCalledTimes(1); + expect(client?.eventListenersRegisteredBeforeConnect()).toBe(true); + }); +}); diff --git a/src/channel/events/simplex-monitor.ts b/src/channel/events/simplex-monitor.ts new file mode 100644 index 0000000..99ae758 --- /dev/null +++ b/src/channel/events/simplex-monitor.ts @@ -0,0 +1,371 @@ +import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import { formatSimplexChatRef, parseSimplexNumericId } from "../../simplex/runtime/api.js"; +import { SimplexClient } from "../../simplex/runtime/client.js"; +import { recordSimplexContactRequest } from "../../simplex/state/contact-requests.js"; +import { markSimplexEventSeen } from "../../simplex/state/event-dedupe.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexChatItem } from "../../types/events.js"; +import type { SimplexChatEvent } from "../../types/simplex.js"; +import { buildComposedMessages, resolveSimplexMediaMaxBytes } from "../media/simplex-media.js"; +import { sendSimplexComposedMessages } from "../messaging/simplex-send.js"; +import { getSimplexRuntime } from "../runtime.js"; +import { connectSimplexWithRetry } from "../transport/simplex-connect.js"; +import { + isInboundSimplexChatItem, + normalizeSimplexSenderId, + resolveSimplexChatContext, + resolveSimplexMessageText, +} from "./simplex-event-parser.js"; +import { resolveSimplexInboundAccess } from "./simplex-inbound-auth.js"; +import { buildSimplexInboundDispatchContext } from "./simplex-inbound-context.js"; +import { + dispatchInbound, + finalizePendingFile, + hasPendingFile, + type PendingInboundFile, + queuePendingFile, + requestFileDownload, +} from "./simplex-inbound-files.js"; + +export type SimplexMonitorOpts = { + account: ResolvedSimplexAccount; + cfg: OpenClawConfig; + runtime: RuntimeEnv; + abortSignal: AbortSignal; + statusSink?: (patch: Partial) => void; +}; + +async function sendSimplexPayload(params: { + client: SimplexClient; + chatRef: string; + cfg: OpenClawConfig; + accountId: string; + payload: { + text?: string; + mediaUrl?: string; + mediaUrls?: string[]; + audioAsVoice?: boolean; + replyToId?: string | number | null; + }; +}): Promise<{ messageId?: string }> { + const quotedItemId = + params.payload.replyToId === undefined || params.payload.replyToId === null + ? undefined + : parseSimplexNumericId(params.payload.replyToId); + const composedMessages = await buildComposedMessages({ + cfg: params.cfg, + accountId: params.accountId, + text: params.payload.text, + mediaUrl: params.payload.mediaUrl, + mediaUrls: params.payload.mediaUrls, + audioAsVoice: params.payload.audioAsVoice, + quotedItemId: quotedItemId ?? undefined, + }); + if (composedMessages.length === 0) { + return {}; + } + return await sendSimplexComposedMessages({ + chatRef: params.chatRef, + composedMessages, + send: (chatRef, messages) => + params.client.sendMessages({ chatRef, composedMessages: messages }), + }); +} + +export async function startSimplexMonitor(params: SimplexMonitorOpts): Promise<{ + client: SimplexClient; +}> { + const { account, cfg, runtime, statusSink } = params; + const client = new SimplexClient({ + account, + logger: { + info: (message) => runtime?.log?.(message), + warn: (message) => runtime?.error?.(message), + error: (message) => runtime?.error?.(message), + }, + }); + + let initialConnectComplete = false; + let reconnecting: Promise | null = null; + const reconnectAfterUnexpectedDisconnect = () => { + if (params.abortSignal.aborted || reconnecting) { + return; + } + statusSink?.({ + connected: false, + running: true, + healthState: "starting", + }); + const reconnect = connectSimplexWithRetry({ + client, + runtime, + accountId: account.accountId, + abortSignal: params.abortSignal, + }) + .catch((err) => { + if (!params.abortSignal.aborted) { + runtime.error?.(`[${account.accountId}] SimpleX reconnect failed: ${String(err)}`); + statusSink?.({ + connected: false, + running: true, + lastError: err instanceof Error ? err.message : String(err), + healthState: "error", + }); + } + }) + .finally(() => { + if (reconnecting === reconnect) { + reconnecting = null; + } + }); + reconnecting = reconnect; + }; + + const stopConnectionState = client.onConnectionState((state) => { + if (state.connected) { + statusSink?.({ + connected: true, + running: true, + lastConnectedAt: state.at, + lastDisconnect: null, + lastError: null, + healthState: "healthy", + }); + return; + } + + statusSink?.({ + connected: false, + running: state.expected ? false : undefined, + lastDisconnect: state.error ? { at: state.at, error: state.error } : { at: state.at }, + ...(state.error ? { lastError: state.error } : {}), + healthState: state.expected ? "stopped" : "disconnected", + }); + if (!state.expected && initialConnectComplete) { + reconnectAfterUnexpectedDisconnect(); + } + }); + + const stopListening = client.onEvent(async (event) => { + try { + await handleSimplexEvent({ event, account, cfg, runtime, statusSink, client }); + } catch (err) { + runtime.error?.(`[${account.accountId}] SimpleX event error: ${String(err)}`); + } + }); + + await connectSimplexWithRetry({ + client, + runtime, + accountId: account.accountId, + abortSignal: params.abortSignal, + }); + initialConnectComplete = true; + + params.abortSignal.addEventListener( + "abort", + () => { + stopConnectionState(); + stopListening(); + client.close().catch((err) => { + runtime.error?.(`[${account.accountId}] SimpleX close failed: ${String(err)}`); + }); + }, + { once: true } + ); + + return { client }; +} + +async function handleSimplexEvent(params: { + event: SimplexChatEvent; + account: ResolvedSimplexAccount; + cfg: OpenClawConfig; + runtime: RuntimeEnv; + statusSink?: (patch: Partial) => void; + client: SimplexClient; +}): Promise { + const { event, account, cfg, runtime, statusSink, client } = params; + statusSink?.({ lastEventAt: Date.now() }); + if (event.type === "receivedContactRequest") { + await recordSimplexContactRequest({ + accountId: account.accountId, + contactRequest: (event as { contactRequest?: unknown }).contactRequest, + }); + return; + } + + if (event.type === "rcvFileDescrReady") { + const fileId = (event as { rcvFileTransfer?: { fileId?: unknown } })?.rcvFileTransfer?.fileId; + if (typeof fileId === "number" && Number.isInteger(fileId) && fileId > 0) { + await requestFileDownload({ fileId, account, client, runtime }); + } + return; + } + + if (event.type === "rcvFileComplete") { + const chatItem = (event as { chatItem?: SimplexChatItem })?.chatItem; + const file = chatItem?.chatItem?.file; + const fileId = typeof file?.fileId === "number" ? file.fileId : null; + if (fileId && hasPendingFile(account.accountId, fileId)) { + const filePath = file?.fileSource?.filePath?.trim(); + await finalizePendingFile({ + accountId: account.accountId, + fileId, + filePath, + }); + } + return; + } + + if (event.type !== "newChatItems") { + return; + } + + const chatItems = event.chatItems; + const items = Array.isArray(chatItems) ? (chatItems as SimplexChatItem[]) : []; + + for (const item of items) { + if (!isInboundSimplexChatItem(item)) { + continue; + } + + const context = resolveSimplexChatContext(item); + if (!context) { + continue; + } + + const content = + item.chatItem?.content?.type === "rcvMsgContent" + ? item.chatItem?.content?.msgContent + : undefined; + + if (!content) { + continue; + } + + const rawBody = resolveSimplexMessageText(content, item.chatItem?.file?.fileName); + if (!rawBody) { + continue; + } + + const currentMessageId = + typeof item.chatItem?.meta?.itemId === "number" ? item.chatItem.meta.itemId : undefined; + + const normalizedSenderId = normalizeSimplexSenderId(context.senderId); + const dmPeerId = normalizedSenderId ?? String(context.chatId); + const chatRef = formatSimplexChatRef({ + type: context.chatType, + id: context.chatType === "group" ? context.chatId : dmPeerId, + }); + + if ( + currentMessageId !== undefined && + !(await markSimplexEventSeen({ + accountId: account.accountId, + chatId: context.chatId, + messageId: currentMessageId, + })) + ) { + continue; + } + + const core = getSimplexRuntime(); + + const route = core.channel.routing.resolveAgentRoute({ + cfg, + channel: SIMPLEX_CHANNEL_ID, + accountId: account.accountId, + peer: { + kind: context.chatType === "group" ? "group" : "direct", + id: context.chatType === "group" ? String(context.chatId) : dmPeerId, + }, + }); + + const access = await resolveSimplexInboundAccess({ + account, + cfg, + runtime, + core, + context, + rawBody, + normalizedSenderId, + routeAgentId: route.agentId, + replyToPairingRequest: async (text) => { + await sendSimplexPayload({ + client, + chatRef, + cfg, + accountId: account.accountId, + payload: { + text, + replyToId: currentMessageId, + }, + }); + statusSink?.({ lastOutboundAt: Date.now() }); + }, + }); + if (!access.allowed) { + continue; + } + + const { ctxPayload, storePath } = buildSimplexInboundDispatchContext({ + core, + cfg, + context, + route, + rawBody, + dmPeerId, + currentMessageId, + effectiveWasMentioned: access.effectiveWasMentioned, + commandAuthorized: access.commandAuthorized, + }); + + const fileId = item.chatItem?.file?.fileId; + const fileSize = item.chatItem?.file?.fileSize; + const maxBytes = resolveSimplexMediaMaxBytes({ + cfg, + accountId: account.accountId, + }); + + const pending: PendingInboundFile = { + fileId: typeof fileId === "number" ? fileId : -1, + ctxPayload, + storePath, + sessionKey: route.sessionKey, + account, + cfg, + runtime, + client, + sendPayload: async (payload) => { + await sendSimplexPayload({ + client, + chatRef, + cfg, + accountId: account.accountId, + payload: { ...payload, replyToId: currentMessageId }, + }); + }, + statusSink, + }; + + if (typeof fileId === "number") { + if (typeof fileSize === "number" && fileSize > maxBytes) { + runtime.error?.( + `[${account.accountId}] SimpleX file ${fileId} exceeds limit (${fileSize} > ${maxBytes})` + ); + continue; + } + const accepted = await requestFileDownload({ fileId, account, client, runtime }); + if (accepted) { + queuePendingFile({ pending, accountId: account.accountId, fileId }); + continue; + } + } + + await dispatchInbound({ pending, mediaPath: undefined, mediaType: undefined }); + } +} diff --git a/src/channel/gateway/simplex-gateway-runtime.test.ts b/src/channel/gateway/simplex-gateway-runtime.test.ts new file mode 100644 index 0000000..989bba7 --- /dev/null +++ b/src/channel/gateway/simplex-gateway-runtime.test.ts @@ -0,0 +1,96 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import { describe, expect, it, vi } from "vitest"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; + +const monitorMock = vi.hoisted(() => ({ + close: vi.fn(async () => undefined), + statusSink: undefined as undefined | ((status: Record) => void), + start: vi.fn(async (opts: { statusSink?: (status: Record) => void }) => { + monitorMock.statusSink = opts.statusSink; + return { + client: { + close: monitorMock.close, + }, + }; + }), +})); + +vi.mock("../events/simplex-monitor.js", () => ({ + startSimplexMonitor: monitorMock.start, +})); + +import { buildSimplexGatewayRuntime } from "./simplex-gateway-runtime.js"; + +function account(): ResolvedSimplexAccount { + return { + accountId: "default", + name: "main", + enabled: true, + configured: true, + mode: "external", + wsUrl: "ws://127.0.0.1:5225", + wsHost: "127.0.0.1", + wsPort: 5225, + config: { + connection: { + wsHost: "127.0.0.1", + wsPort: 5225, + }, + }, + }; +} + +describe("simplex gateway runtime", () => { + it("seeds configured running status before waiting on the external runtime", async () => { + const gateway = buildSimplexGatewayRuntime(); + const abort = new AbortController(); + const statuses: unknown[] = []; + type StartContext = Parameters>[0]; + + const running = gateway.startAccount?.({ + account: account(), + accountId: "default", + cfg: { channels: {} } as OpenClawConfig, + runtime: {} as RuntimeEnv, + abortSignal: abort.signal, + log: { + info() {}, + warn() {}, + error() {}, + }, + setStatus: (status: unknown) => statuses.push(status), + getStatus: () => ({ accountId: "default" }), + } satisfies StartContext); + + await vi.waitFor(() => expect(statuses.length).toBeGreaterThan(0)); + + expect(statuses[0]).toMatchObject({ + accountId: "default", + running: true, + connected: false, + lastStopAt: null, + lastError: null, + healthState: "starting", + }); + + await vi.waitFor(() => expect(monitorMock.start).toHaveBeenCalled()); + monitorMock.statusSink?.({ + connected: true, + running: true, + lastConnectedAt: 123, + healthState: "healthy", + }); + + expect(statuses.at(-1)).toMatchObject({ + accountId: "default", + running: true, + connected: true, + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + abort.abort(); + await running; + expect(monitorMock.close).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/channel/gateway/simplex-gateway-runtime.ts b/src/channel/gateway/simplex-gateway-runtime.ts new file mode 100644 index 0000000..749049c --- /dev/null +++ b/src/channel/gateway/simplex-gateway-runtime.ts @@ -0,0 +1,69 @@ +import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; +import { createAccountStatusSink, waitUntilAbort } from "openclaw/plugin-sdk/channel-runtime"; +import { + getActiveSimplexClient, + registerActiveSimplexClient, + unregisterActiveSimplexClient, +} from "../../simplex/runtime/transport.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { startSimplexMonitor } from "../events/simplex-monitor.js"; + +export function buildSimplexGatewayRuntime(): NonNullable< + ChannelPlugin["gateway"] +> { + return { + startAccount: async (ctx) => { + const account = ctx.account; + const startedAt = Date.now(); + const setStatus = createAccountStatusSink({ + accountId: account.accountId, + setStatus: ctx.setStatus, + }); + ctx.log?.info?.(`[${account.accountId}] SimpleX start requested (mode=${account.mode})`); + setStatus({ + connected: false, + running: true, + lastStartAt: startedAt, + lastStopAt: null, + lastError: null, + healthState: account.configured ? "starting" : "idle", + }); + + ctx.log?.info?.(`[${account.accountId}] Starting SimpleX monitor`); + const monitor = await startSimplexMonitor({ + account, + cfg: ctx.cfg, + runtime: ctx.runtime, + abortSignal: ctx.abortSignal, + statusSink: setStatus, + }).catch((err) => { + if (!ctx.abortSignal.aborted) { + setStatus({ + running: false, + connected: false, + lastStopAt: Date.now(), + lastError: err instanceof Error ? err.message : String(err), + healthState: "error", + }); + } + throw err; + }); + ctx.log?.info?.(`[${account.accountId}] SimpleX monitor started`); + + await registerActiveSimplexClient(account, monitor.client); + try { + await waitUntilAbort(ctx.abortSignal); + } finally { + unregisterActiveSimplexClient(account, monitor.client); + await monitor.client.close().catch(() => undefined); + } + }, + stopAccount: async (ctx) => { + const client = getActiveSimplexClient(ctx.account.accountId); + if (client) { + await client.close(); + unregisterActiveSimplexClient(ctx.account, client); + } + }, + }; +} diff --git a/src/channel/gateway/simplex-heartbeat.test.ts b/src/channel/gateway/simplex-heartbeat.test.ts new file mode 100644 index 0000000..0dc4261 --- /dev/null +++ b/src/channel/gateway/simplex-heartbeat.test.ts @@ -0,0 +1,41 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { describe, expect, it } from "vitest"; +import { buildSimplexHeartbeat } from "./simplex-heartbeat.js"; + +describe("simplex heartbeat adapter", () => { + it("reports not configured accounts as not ready", async () => { + const heartbeat = buildSimplexHeartbeat(); + + await expect( + heartbeat.checkReady?.({ + cfg: { channels: {} } as OpenClawConfig, + }) + ).resolves.toEqual({ + ok: false, + reason: "simplex-not-configured", + }); + }); + + it("reports configured but stopped accounts as not running", async () => { + const heartbeat = buildSimplexHeartbeat(); + + await expect( + heartbeat.checkReady?.({ + cfg: { + channels: { + "openclaw-simplex": { + connection: { wsUrl: "ws://127.0.0.1:5225" }, + }, + }, + } as OpenClawConfig, + }) + ).resolves.toEqual({ + ok: false, + reason: "simplex-not-running", + }); + }); + + it("does not expose removed heartbeat recipient resolution hooks", () => { + expect(buildSimplexHeartbeat()).not.toHaveProperty("resolveRecipients"); + }); +}); diff --git a/src/channel/gateway/simplex-heartbeat.ts b/src/channel/gateway/simplex-heartbeat.ts new file mode 100644 index 0000000..62ba14e --- /dev/null +++ b/src/channel/gateway/simplex-heartbeat.ts @@ -0,0 +1,21 @@ +import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; +import { resolveSimplexAccount } from "../../config/accounts.js"; +import { hasActiveSimplexClient } from "../../simplex/runtime/transport.js"; + +export function buildSimplexHeartbeat(): NonNullable { + return { + checkReady: async ({ cfg, accountId }) => { + const account = resolveSimplexAccount({ cfg, accountId }); + if (!account.enabled) { + return { ok: false as const, reason: "simplex-disabled" as const }; + } + if (!account.configured) { + return { ok: false as const, reason: "simplex-not-configured" as const }; + } + if (!hasActiveSimplexClient(account.accountId)) { + return { ok: false as const, reason: "simplex-not-running" as const }; + } + return { ok: true as const, reason: "ok" as const }; + }, + }; +} diff --git a/src/channel/simplex-media.ts b/src/channel/media/simplex-media.ts similarity index 91% rename from src/channel/simplex-media.ts rename to src/channel/media/simplex-media.ts index b9415e5..459af51 100644 --- a/src/channel/simplex-media.ts +++ b/src/channel/media/simplex-media.ts @@ -1,9 +1,9 @@ import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime"; -import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import type { SimplexComposedMessage, SimplexMsgContent } from "../simplex/simplex-commands.js"; -import { getSimplexRuntime } from "./runtime.js"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import type { SimplexComposedMessage, SimplexMsgContent } from "../../types/simplex.js"; +import { getSimplexRuntime } from "../runtime.js"; const DEFAULT_MAX_BYTES = 5 * 1024 * 1024; @@ -22,7 +22,7 @@ export function resolveSimplexMediaMaxBytes(params: { ); } -export async function resolveMediaPath(params: { +async function resolveMediaPath(params: { mediaUrl: string; maxBytes: number; }): Promise<{ path: string; contentType?: string; fileName?: string }> { @@ -48,7 +48,7 @@ export async function resolveMediaPath(params: { return { path: params.mediaUrl, contentType, fileName }; } -export function buildMediaMsgContent(params: { +function buildMediaMsgContent(params: { text: string; mediaPath: string; contentType?: string; @@ -99,6 +99,7 @@ export async function buildComposedMessages(params: { mediaUrls?: string[]; mediaUrl?: string; audioAsVoice?: boolean; + quotedItemId?: number; }): Promise { const text = params.text ?? ""; const mediaList = params.mediaUrls?.length @@ -112,6 +113,8 @@ export async function buildComposedMessages(params: { if (text) { composedMessages.push({ msgContent: { type: "text", text }, + quotedItemId: params.quotedItemId, + mentions: {}, }); } return composedMessages; @@ -139,6 +142,8 @@ export async function buildComposedMessages(params: { composedMessages.push({ fileSource: { filePath: resolved.path }, msgContent, + quotedItemId: params.quotedItemId, + mentions: {}, }); } diff --git a/src/channel/simplex-outbound.test.ts b/src/channel/messaging/simplex-outbound.test.ts similarity index 91% rename from src/channel/simplex-outbound.test.ts rename to src/channel/messaging/simplex-outbound.test.ts index bd960c0..1c62d0c 100644 --- a/src/channel/simplex-outbound.test.ts +++ b/src/channel/messaging/simplex-outbound.test.ts @@ -15,7 +15,7 @@ describe("simplex outbound presentation support", () => { }); it("advertises presentation support with text fallback only", () => { - const outbound = buildSimplexOutbound(new Map()); + const outbound = buildSimplexOutbound(); expect( outbound.shouldTreatDeliveredTextAsVisible?.({ @@ -40,7 +40,7 @@ describe("simplex outbound presentation support", () => { }); it("renders presentation payloads into fallback text", async () => { - const outbound = buildSimplexOutbound(new Map()); + const outbound = buildSimplexOutbound(); const rendered = await outbound.renderPresentation?.({ payload: { text: "Choose an option", @@ -70,15 +70,13 @@ describe("simplex outbound presentation support", () => { }); it("renders polls into numbered text prompts", async () => { - const outbound = buildSimplexOutbound(new Map()); + const outbound = buildSimplexOutbound(); const result = await outbound.sendPoll?.({ cfg: { channels: { "openclaw-simplex": { - connection: { - wsUrl: "ws://127.0.0.1:5225", - }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, }, }, }, diff --git a/src/channel/simplex-outbound.ts b/src/channel/messaging/simplex-outbound.ts similarity index 83% rename from src/channel/simplex-outbound.ts rename to src/channel/messaging/simplex-outbound.ts index 197d9bf..81a38ba 100644 --- a/src/channel/simplex-outbound.ts +++ b/src/channel/messaging/simplex-outbound.ts @@ -1,14 +1,13 @@ import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; import { renderMessagePresentationFallbackText } from "openclaw/plugin-sdk/interactive-runtime"; import { normalizePollInput } from "openclaw/plugin-sdk/poll-runtime"; -import { resolveSimplexAccount } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import type { SimplexClientRegistry } from "./simplex-client-registry.js"; -import { assertSimplexOutboundAccountReady } from "./simplex-common.js"; +import { resolveSimplexAccount } from "../../config/accounts.js"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import { assertSimplexOutboundAccountReady } from "../shared/simplex-common.js"; import { buildAndSendSimplexMessages } from "./simplex-send.js"; -function renderSimplexPollText(params: { +export function renderSimplexPollText(params: { question: string; options: string[]; maxSelections: number; @@ -43,9 +42,9 @@ function renderSimplexPollText(params: { return lines.join("\n").trim(); } -export function buildSimplexOutbound( - registry: SimplexClientRegistry -): NonNullable["outbound"]> { +export function buildSimplexOutbound(): NonNullable< + ChannelPlugin["outbound"] +> { return { deliveryMode: "direct" as const, textChunkLimit: 4000, @@ -66,11 +65,10 @@ export function buildSimplexOutbound( presentation, }), }), - sendPayload: async ({ cfg, to, payload, accountId }) => { + sendPayload: async ({ cfg, to, payload, accountId, replyToId }) => { const account = resolveSimplexAccount({ cfg, accountId }); assertSimplexOutboundAccountReady(account); const result = await buildAndSendSimplexMessages({ - registry, cfg, account, chatRef: to, @@ -78,6 +76,7 @@ export function buildSimplexOutbound( mediaUrls: payload.mediaUrls, mediaUrl: payload.mediaUrl, audioAsVoice: payload.audioAsVoice, + replyToId, }); return { channel: SIMPLEX_CHANNEL_ID, @@ -85,15 +84,15 @@ export function buildSimplexOutbound( chatId: to, }; }, - sendText: async ({ cfg, to, text, accountId }) => { + sendText: async ({ cfg, to, text, accountId, replyToId }) => { const account = resolveSimplexAccount({ cfg, accountId }); assertSimplexOutboundAccountReady(account); const result = await buildAndSendSimplexMessages({ - registry, cfg, account, chatRef: to, text, + replyToId, }); return { channel: SIMPLEX_CHANNEL_ID, @@ -101,19 +100,19 @@ export function buildSimplexOutbound( chatId: to, }; }, - sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => { + sendMedia: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => { if (!mediaUrl) { return { channel: SIMPLEX_CHANNEL_ID, messageId: "empty", chatId: to }; } const account = resolveSimplexAccount({ cfg, accountId }); assertSimplexOutboundAccountReady(account); const result = await buildAndSendSimplexMessages({ - registry, cfg, account, chatRef: to, text, mediaUrl, + replyToId, }); return { channel: SIMPLEX_CHANNEL_ID, @@ -128,7 +127,6 @@ export function buildSimplexOutbound( const normalized = normalizePollInput(poll); const text = renderSimplexPollText(normalized); const result = await buildAndSendSimplexMessages({ - registry, cfg, account, chatRef: to, diff --git a/src/channel/messaging/simplex-send.ts b/src/channel/messaging/simplex-send.ts new file mode 100644 index 0000000..5cf5bfe --- /dev/null +++ b/src/channel/messaging/simplex-send.ts @@ -0,0 +1,52 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { parseSimplexNumericId, resolveSimplexChatItemId } from "../../simplex/runtime/api.js"; +import { withSimplexClient } from "../../simplex/runtime/transport.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexComposedMessage } from "../../types/simplex.js"; +import { buildComposedMessages } from "../media/simplex-media.js"; + +export async function sendSimplexComposedMessages(params: { + chatRef: string; + composedMessages: SimplexComposedMessage[]; + send: (chatRef: string, composedMessages: SimplexComposedMessage[]) => Promise; +}): Promise<{ messageId?: string }> { + if (params.composedMessages.length === 0) { + return {}; + } + const chatItems = await params.send(params.chatRef, params.composedMessages); + return { messageId: resolveSimplexChatItemId(chatItems[0]) }; +} + +export async function buildAndSendSimplexMessages(params: { + cfg: OpenClawConfig; + account: ResolvedSimplexAccount; + chatRef: string; + text?: string; + mediaUrl?: string; + mediaUrls?: string[]; + audioAsVoice?: boolean; + replyToId?: string | number | null; +}): Promise<{ messageId?: string }> { + const quotedItemId = + params.replyToId === undefined || params.replyToId === null + ? undefined + : parseSimplexNumericId(params.replyToId); + const composedMessages = await buildComposedMessages({ + cfg: params.cfg, + accountId: params.account.accountId, + text: params.text, + mediaUrl: params.mediaUrl, + mediaUrls: params.mediaUrls, + audioAsVoice: params.audioAsVoice, + quotedItemId: quotedItemId ?? undefined, + }); + return await sendSimplexComposedMessages({ + chatRef: params.chatRef, + composedMessages, + send: (chatRef, messages) => + withSimplexClient({ + account: params.account, + run: (client) => client.sendMessages({ chatRef, composedMessages: messages }), + }), + }); +} diff --git a/src/channel/plugin.ts b/src/channel/plugin.ts index 0d036e3..e34fa0e 100644 --- a/src/channel/plugin.ts +++ b/src/channel/plugin.ts @@ -15,11 +15,27 @@ import { SIMPLEX_ACCOUNT_CONFIG_CLEAR_FIELDS, SimplexChannelConfigSchema, } from "../config/config-schema.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import type { SimplexWsClient } from "../simplex/simplex-ws-client.js"; -import { simplexApprovalAuth } from "./approval-auth.js"; -import { simplexCommandPolicy } from "./command-policy.js"; +import type { ResolvedSimplexAccount } from "../types/config.js"; +import { + listSimplexDirectoryGroups, + listSimplexDirectoryPeers, + listSimplexGroupMembers, + resolveSimplexSelf, + resolveSimplexTargets, +} from "./contacts/simplex-directory.js"; +import { buildSimplexPairing } from "./contacts/simplex-pairing.js"; +import { simplexDoctor } from "./diagnostics/simplex-doctor.js"; +import { buildSimplexStatus } from "./diagnostics/simplex-status.js"; +import { buildSimplexGatewayRuntime } from "./gateway/simplex-gateway-runtime.js"; +import { buildSimplexHeartbeat } from "./gateway/simplex-heartbeat.js"; +import { buildSimplexOutbound } from "./messaging/simplex-outbound.js"; +import { simplexApprovalAuth } from "./security/approval-auth.js"; +import { simplexCommandPolicy } from "./security/command-policy.js"; +import { + collectSimplexSecurityAuditFindings, + formatSimplexAllowFrom, +} from "./security/simplex-security.js"; import { simplexSetupAdapter } from "./setup.js"; import { formatSimplexTargetDisplay, @@ -27,25 +43,10 @@ import { parseSimplexExplicitTarget, resolveSimplexGroupRequireMention, resolveSimplexGroupToolPolicy, + resolveSimplexRouteTarget, stripLeadingAt, stripSimplexPrefix, -} from "./simplex-common.js"; -import { - listSimplexDirectoryGroups, - listSimplexDirectoryPeers, - listSimplexGroupMembers, - resolveSimplexSelf, - resolveSimplexTargets, -} from "./simplex-directory.js"; -import { simplexDoctor } from "./simplex-doctor.js"; -import { buildSimplexGatewayRuntime } from "./simplex-gateway-runtime.js"; -import { buildSimplexHeartbeat } from "./simplex-heartbeat.js"; -import { buildSimplexOutbound } from "./simplex-outbound.js"; -import { buildSimplexPairing } from "./simplex-pairing.js"; -import { formatSimplexAllowFrom } from "./simplex-security.js"; -import { buildSimplexStatus } from "./simplex-status.js"; - -const activeClients = new Map(); +} from "./shared/simplex-common.js"; const resolveSimplexDmSecurityPolicy = createScopedDmSecurityResolver({ channelKey: SIMPLEX_CHANNEL_ID, @@ -65,11 +66,11 @@ export const simplexPlugin: ChannelPlugin = { meta: { id: SIMPLEX_CHANNEL_ID, label: "SimpleX", - selectionLabel: "SimpleX (WebSocket)", + selectionLabel: "SimpleX", detailLabel: "SimpleX Chat", docsPath: "/channels/openclaw-simplex", docsLabel: SIMPLEX_CHANNEL_ID, - blurb: "SimpleX Chat via external WebSocket API", + blurb: "SimpleX Chat via an external WebSocket runtime", aliases: ["simplex"], order: 95, systemImage: "link.badge.plus", @@ -82,7 +83,7 @@ export const simplexPlugin: ChannelPlugin = { }, quickstartAllowFrom: true, }, - pairing: buildSimplexPairing(activeClients), + pairing: buildSimplexPairing(), capabilities: { chatTypes: ["direct", "group"], polls: true, @@ -102,7 +103,6 @@ export const simplexPlugin: ChannelPlugin = { listAccountIds: (cfg) => listSimplexAccountIds(cfg), resolveAccount: resolveSimplexConfigAccount, defaultAccountId: (cfg) => resolveDefaultSimplexAccountId(cfg), - inspectAccount: (cfg, accountId) => resolveSimplexConfigAccount(cfg, accountId).config, clearBaseFields: SIMPLEX_ACCOUNT_CONFIG_CLEAR_FIELDS, preserveSectionOnDefaultDelete: true, resolveAllowFrom: (account) => account.config.allowFrom, @@ -130,8 +130,16 @@ export const simplexPlugin: ChannelPlugin = { resolveGroupPolicy: (account) => account.config.groupPolicy, }), messaging: { + targetPrefixes: ["simplex"], normalizeTarget: (raw) => stripSimplexPrefix(raw), parseExplicitTarget: ({ raw }) => parseSimplexExplicitTarget(raw), + resolveSessionConversation: ({ kind, rawId }) => { + const target = + kind === "group" || kind === "channel" + ? resolveSimplexRouteTarget({ rawTarget: rawId }) + : null; + return target ? { id: target.to, threadId: target.threadId ?? null } : null; + }, inferTargetChatType: ({ to }) => inferSimplexTargetChatType(to), formatTargetDisplay: (params) => formatSimplexTargetDisplay(params), targetResolver: { @@ -179,15 +187,32 @@ export const simplexPlugin: ChannelPlugin = { `- SimpleX groups: groupPolicy="open" allows any member to trigger the bot. Set channels.${SIMPLEX_CHANNEL_ID}.groupPolicy="allowlist" + channels.${SIMPLEX_CHANNEL_ID}.groupAllowFrom to restrict senders.`, ]; }, + collectAuditFindings: ({ account, cfg }) => + collectSimplexSecurityAuditFindings({ account, cfg }), }, groups: { resolveRequireMention: resolveSimplexGroupRequireMention, resolveToolPolicy: resolveSimplexGroupToolPolicy, }, - gatewayMethods: ["simplex.invite.create", "simplex.invite.list", "simplex.invite.revoke"], - outbound: buildSimplexOutbound(activeClients), - heartbeat: buildSimplexHeartbeat(activeClients), - status: buildSimplexStatus(activeClients), + gatewayMethods: [ + "simplex.invite.create", + "simplex.invite.list", + "simplex.invite.revoke", + "simplex.runtime.status", + "simplex.runtime.doctor", + "simplex.requests.list", + "simplex.requests.accept", + "simplex.requests.reject", + "simplex.groups.create", + "simplex.groups.link.create", + "simplex.groups.link.list", + "simplex.groups.link.revoke", + "simplex.connect.plan", + "simplex.connect", + ], + outbound: buildSimplexOutbound(), + heartbeat: buildSimplexHeartbeat(), + status: buildSimplexStatus(), doctor: simplexDoctor, - gateway: buildSimplexGatewayRuntime(activeClients), + gateway: buildSimplexGatewayRuntime(), }; diff --git a/src/channel/approval-auth.test.ts b/src/channel/security/approval-auth.test.ts similarity index 100% rename from src/channel/approval-auth.test.ts rename to src/channel/security/approval-auth.test.ts diff --git a/src/channel/approval-auth.ts b/src/channel/security/approval-auth.ts similarity index 85% rename from src/channel/approval-auth.ts rename to src/channel/security/approval-auth.ts index f8831a9..971dbec 100644 --- a/src/channel/approval-auth.ts +++ b/src/channel/security/approval-auth.ts @@ -2,8 +2,8 @@ import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers, } from "openclaw/plugin-sdk/approval-auth-runtime"; -import { resolveSimplexAccount } from "../config/accounts.js"; -import { normalizeSimplexContactRef } from "./simplex-common.js"; +import { resolveSimplexAccount } from "../../config/accounts.js"; +import { normalizeSimplexContactRef } from "../shared/simplex-common.js"; function normalizeSimplexApproverId(value: string | number): string | undefined { const normalized = normalizeSimplexContactRef(String(value)); diff --git a/src/channel/command-policy.ts b/src/channel/security/command-policy.ts similarity index 100% rename from src/channel/command-policy.ts rename to src/channel/security/command-policy.ts diff --git a/src/channel/simplex-security.test.ts b/src/channel/security/simplex-security.test.ts similarity index 75% rename from src/channel/simplex-security.test.ts rename to src/channel/security/simplex-security.test.ts index 1408a7c..d9fe872 100644 --- a/src/channel/simplex-security.test.ts +++ b/src/channel/security/simplex-security.test.ts @@ -1,7 +1,8 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; import { describe, expect, it } from "vitest"; -import type { ResolvedSimplexAccount } from "../config/types.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; import { + collectSimplexSecurityAuditFindings, formatSimplexAllowFrom, isSimplexAllowlisted, parseSimplexAllowlistEntry, @@ -72,9 +73,7 @@ describe("simplex allowlist", () => { accounts: { beta: { allowFrom: ["beta-only"], - connection: { - wsUrl: "ws://127.0.0.1:7777", - }, + connection: { wsUrl: "ws://127.0.0.1:5225" }, }, }, }, @@ -110,7 +109,7 @@ describe("simplex allowlist", () => { wsUrl: "ws://127.0.0.1:5225", wsHost: "127.0.0.1", wsPort: 5225, - config: { markdown: {}, dmPolicy: "open" }, + config: { markdown: {}, allowFrom: [], groupAllowFrom: [], dmPolicy: "open" }, }; const result = resolveSimplexDmPolicy({ cfg, @@ -155,4 +154,36 @@ describe("simplex allowlist", () => { }) ).toBe(true); }); + + it("audits open policies and unsafe websocket endpoints", () => { + const findings = collectSimplexSecurityAuditFindings({ + cfg: { channels: {} } as OpenClawConfig, + account: { + accountId: "alpha", + enabled: true, + configured: true, + mode: "external", + wsUrl: "ws://example.com:5225?token=secret", + wsHost: "example.com", + wsPort: 5225, + config: { + allowFrom: [], + groupAllowFrom: [], + dmPolicy: "open", + groupPolicy: "open", + connection: { + wsUrl: "ws://example.com:5225?token=secret", + }, + }, + }, + }); + + expect(findings.map((finding) => finding.checkId)).toEqual([ + "simplex.dm-policy-open", + "simplex.group-policy-open", + "simplex.ws-endpoint-blocked", + ]); + expect(findings[2]?.detail).toContain("?redacted"); + expect(findings[2]?.detail).not.toContain("secret"); + }); }); diff --git a/src/channel/simplex-security.ts b/src/channel/security/simplex-security.ts similarity index 59% rename from src/channel/simplex-security.ts rename to src/channel/security/simplex-security.ts index f76dd4b..ac17b26 100644 --- a/src/channel/simplex-security.ts +++ b/src/channel/security/simplex-security.ts @@ -1,12 +1,17 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import { resolveDefaultSimplexAccountId } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import { stripSimplexPrefix } from "./simplex-common.js"; +import { resolveDefaultSimplexAccountId } from "../../config/accounts.js"; +import { SIMPLEX_CHANNEL_ID } from "../../constants.js"; +import { describeSimplexWsEndpointSecurity } from "../../simplex/runtime/security.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexAllowlistEntry } from "../../types/security.js"; +import { stripSimplexPrefix } from "../shared/simplex-common.js"; -export type SimplexAllowlistEntry = { - kind: "any" | "sender" | "group"; - value: string; +type SimplexAuditFinding = { + checkId: string; + severity: "info" | "warn" | "critical"; + title: string; + detail: string; + remediation?: string; }; function normalizeSimplexId(value: string): string { @@ -123,3 +128,54 @@ export function isSimplexAllowlisted(params: { } return false; } + +export function collectSimplexSecurityAuditFindings(params: { + account: ResolvedSimplexAccount; + cfg: OpenClawConfig; +}): SimplexAuditFinding[] { + const { account, cfg } = params; + const findings: SimplexAuditFinding[] = []; + const dmPolicy = account.config.dmPolicy ?? "pairing"; + const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy; + const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist"; + + if (dmPolicy === "open") { + findings.push({ + checkId: "simplex.dm-policy-open", + severity: "warn", + title: "SimpleX DMs accept any contact", + detail: 'dmPolicy="open" lets any SimpleX contact reaching this account trigger the agent.', + remediation: + 'Use dmPolicy="pairing" for invite-based approval or dmPolicy="allowlist" with allowFrom.', + }); + } + + if (groupPolicy === "open") { + findings.push({ + checkId: "simplex.group-policy-open", + severity: "warn", + title: "SimpleX groups accept any member", + detail: 'groupPolicy="open" lets any member of a reachable SimpleX group trigger the agent.', + remediation: 'Use groupPolicy="allowlist" and groupAllowFrom for specific groups/senders.', + }); + } + + const endpoint = describeSimplexWsEndpointSecurity(account.wsUrl, { + allowUnsafeRemoteWs: account.config.connection?.allowUnsafeRemoteWs, + }); + for (const warning of endpoint.warnings) { + findings.push({ + checkId: + endpoint.blockingWarnings.length > 0 + ? "simplex.ws-endpoint-blocked" + : "simplex.ws-endpoint-warning", + severity: endpoint.blockingWarnings.length > 0 ? "critical" : "warn", + title: "SimpleX WebSocket endpoint weakens transport privacy", + detail: warning, + remediation: + "Prefer ws://127.0.0.1, a private sidecar network, or wss:// behind authenticated network controls.", + }); + } + + return findings; +} diff --git a/src/channel/setup.ts b/src/channel/setup.ts index 6e9b498..41203de 100644 --- a/src/channel/setup.ts +++ b/src/channel/setup.ts @@ -30,33 +30,29 @@ export const simplexSetupAdapter: ChannelSetupAdapter = { name, }); }, - applyAccountConfig: ({ cfg, accountId, input }) => { - const wsUrl = input.url?.trim() || input.httpUrl?.trim(); + applyAccountConfig: ({ cfg, accountId }) => { return applySetupAccountConfigPatch({ cfg, channelKey: SIMPLEX_CHANNEL_ID, accountId, patch: { enabled: true, - ...(wsUrl - ? { - connection: { - mode: "external", - wsUrl, - }, - } - : {}), + connection: { + mode: "external", + wsHost: "127.0.0.1", + wsPort: 5225, + }, }, }); }, validateInput: ({ input }) => { const cliPath = input.cliPath?.trim(); if (cliPath) { - return "SimpleX managed mode is no longer supported; run simplex-chat separately and provide a ws:// or wss:// URL instead."; + return "SimpleX CLI path is not configured in OpenClaw. Start simplex-chat separately with its WebSocket API enabled."; } - const wsUrl = input.url?.trim() || input.httpUrl?.trim(); - if (wsUrl && !/^wss?:\/\//i.test(wsUrl)) { - return "SimpleX external URL must start with ws:// or wss://"; + const runtimeUrl = input.url?.trim() || input.httpUrl?.trim(); + if (runtimeUrl && !runtimeUrl.startsWith("ws://") && !runtimeUrl.startsWith("wss://")) { + return "SimpleX runtime URL must be a ws:// or wss:// WebSocket URL."; } return null; }, diff --git a/src/channel/simplex-common.ts b/src/channel/shared/simplex-common.ts similarity index 79% rename from src/channel/simplex-common.ts rename to src/channel/shared/simplex-common.ts index b1b504b..f8058c6 100644 --- a/src/channel/simplex-common.ts +++ b/src/channel/shared/simplex-common.ts @@ -1,30 +1,10 @@ -import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id"; import type { ChannelGroupContext } from "openclaw/plugin-sdk/channel-contract"; import type { GroupToolPolicyConfig } from "openclaw/plugin-sdk/channel-policy"; -import { resolveSimplexAccount } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { stripSimplexProviderPrefix } from "../constants.js"; - -export { DEFAULT_ACCOUNT_ID }; - -export type SimplexExplicitTarget = { - to: string; - chatType: "direct" | "group"; -}; - -type SimplexTargetKind = SimplexExplicitTarget["chatType"] | null; - -export function extractSimplexWsUrlFromApplication(application: unknown): string | undefined { - if (!application || typeof application !== "object") { - return undefined; - } - const value = (application as { wsUrl?: unknown }).wsUrl; - if (typeof value !== "string") { - return undefined; - } - const trimmed = value.trim(); - return trimmed ? trimmed : undefined; -} +import { resolveChannelRouteTargetWithParser } from "openclaw/plugin-sdk/channel-route"; +import { resolveSimplexAccount } from "../../config/accounts.js"; +import { stripSimplexProviderPrefix } from "../../constants.js"; +import type { SimplexExplicitTarget, SimplexTargetKind } from "../../types/channel.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; export function resolveSimplexHealthState(params: { configured: boolean; @@ -48,17 +28,6 @@ export function resolveSimplexHealthState(params: { return "idle"; } -export function normalizeSimplexMessageId(value: unknown): string | undefined { - if (typeof value === "number" && Number.isFinite(value)) { - return String(value); - } - if (typeof value === "string") { - const trimmed = value.trim(); - return trimmed ? trimmed : undefined; - } - return undefined; -} - export function stripSimplexPrefix(value: string): string { return stripSimplexProviderPrefix(value); } @@ -105,6 +74,34 @@ export function parseSimplexExplicitTarget(raw: string): SimplexExplicitTarget | return null; } +export function resolveSimplexRouteTarget(params: { + rawTarget?: string | null; + accountId?: string | null; + fallbackThreadId?: string | number | null; +}): { + to: string; + accountId?: string; + threadId?: string; + chatType?: "direct" | "group"; +} | null { + const route = resolveChannelRouteTargetWithParser({ + channel: "openclaw-simplex", + rawTarget: params.rawTarget, + fallbackThreadId: params.fallbackThreadId, + parseExplicitTarget: (_channel, rawTarget) => parseSimplexExplicitTarget(rawTarget), + }); + if (!route) { + return null; + } + return { + to: route.to, + accountId: + params.accountId ?? (typeof route.accountId === "string" ? route.accountId : undefined), + threadId: route.threadId === undefined ? undefined : String(route.threadId), + chatType: route.chatType === "group" ? "group" : "direct", + }; +} + export function inferSimplexTargetChatType( raw: string ): SimplexExplicitTarget["chatType"] | undefined { diff --git a/src/channel/simplex-client-registry.ts b/src/channel/simplex-client-registry.ts deleted file mode 100644 index 8bdb26f..0000000 --- a/src/channel/simplex-client-registry.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { SimplexWsClient } from "../simplex/simplex-ws-client.js"; - -export type SimplexClientRegistry = Map; - -export async function withSimplexRegistryClient( - registry: SimplexClientRegistry, - account: ResolvedSimplexAccount, - fn: (client: SimplexWsClient) => Promise -): Promise { - const existing = registry.get(account.accountId); - if (existing) { - await existing.connect(); - return await fn(existing); - } - const client = new SimplexWsClient({ - url: account.wsUrl, - connectTimeoutMs: account.config.connection?.connectTimeoutMs, - }); - await client.connect(); - try { - return await fn(client); - } finally { - await client.close(); - } -} diff --git a/src/channel/simplex-gateway-runtime.ts b/src/channel/simplex-gateway-runtime.ts deleted file mode 100644 index 7ea1617..0000000 --- a/src/channel/simplex-gateway-runtime.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import type { SimplexClientRegistry } from "./simplex-client-registry.js"; -import { startSimplexMonitor } from "./simplex-monitor.js"; - -export function buildSimplexGatewayRuntime( - activeClients: SimplexClientRegistry -): NonNullable["gateway"]> { - return { - startAccount: async (ctx) => { - const account = ctx.account; - ctx.log?.info?.(`[${account.accountId}] SimpleX start requested (wsUrl=${account.wsUrl})`); - ctx.setStatus({ - accountId: account.accountId, - mode: account.mode, - application: { wsUrl: account.wsUrl }, - }); - - ctx.log?.info?.(`[${account.accountId}] Starting SimpleX monitor`); - const monitor = await startSimplexMonitor({ - account, - cfg: ctx.cfg, - runtime: ctx.runtime, - abortSignal: ctx.abortSignal, - statusSink: (patch) => ctx.setStatus({ accountId: account.accountId, ...patch }), - }); - ctx.log?.info?.(`[${account.accountId}] SimpleX monitor started`); - - activeClients.set(account.accountId, monitor.client); - - await new Promise((resolve) => { - ctx.abortSignal.addEventListener( - "abort", - () => { - resolve(); - }, - { once: true } - ); - }); - - activeClients.delete(account.accountId); - await monitor.client.close().catch(() => undefined); - }, - stopAccount: async (ctx) => { - const client = activeClients.get(ctx.account.accountId); - if (client) { - await client.close(); - activeClients.delete(ctx.account.accountId); - } - }, - }; -} diff --git a/src/channel/simplex-heartbeat.test.ts b/src/channel/simplex-heartbeat.test.ts deleted file mode 100644 index 631fe98..0000000 --- a/src/channel/simplex-heartbeat.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import { describe, expect, it } from "vitest"; -import { buildSimplexHeartbeat } from "./simplex-heartbeat.js"; - -describe("simplex heartbeat adapter", () => { - it("reports not configured accounts as not ready", async () => { - const heartbeat = buildSimplexHeartbeat(new Map()); - - await expect( - heartbeat.checkReady?.({ - cfg: { channels: {} } as OpenClawConfig, - }) - ).resolves.toEqual({ - ok: false, - reason: "simplex-not-configured", - }); - }); - - it("reports configured but stopped accounts as not running", async () => { - const heartbeat = buildSimplexHeartbeat(new Map()); - - await expect( - heartbeat.checkReady?.({ - cfg: { - channels: { - "openclaw-simplex": { - connection: { wsUrl: "ws://127.0.0.1:5225" }, - }, - }, - } as OpenClawConfig, - }) - ).resolves.toEqual({ - ok: false, - reason: "simplex-not-running", - }); - }); - - it("resolves direct and group recipients from config", () => { - const heartbeat = buildSimplexHeartbeat(new Map()); - - expect( - heartbeat.resolveRecipients?.({ - cfg: { - channels: { - "openclaw-simplex": { - connection: { wsUrl: "ws://127.0.0.1:5225" }, - allowFrom: ["alice", "@bob"], - groupAllowFrom: ["group:ops"], - }, - }, - } as OpenClawConfig, - }) - ).toEqual({ - recipients: ["@alice", "@bob"], - source: "allowFrom", - }); - - expect( - heartbeat.resolveRecipients?.({ - cfg: { - channels: { - "openclaw-simplex": { - connection: { wsUrl: "ws://127.0.0.1:5225" }, - allowFrom: ["alice"], - groupAllowFrom: ["group:ops"], - }, - }, - } as OpenClawConfig, - opts: { all: true }, - }) - ).toEqual({ - recipients: ["@alice", "#ops"], - source: "all", - }); - }); - - it("prefers an explicit recipient override", () => { - const heartbeat = buildSimplexHeartbeat(new Map()); - - expect( - heartbeat.resolveRecipients?.({ - cfg: { channels: {} } as OpenClawConfig, - opts: { to: "#ops" }, - }) - ).toEqual({ - recipients: ["#ops"], - source: "flag", - }); - }); -}); diff --git a/src/channel/simplex-heartbeat.ts b/src/channel/simplex-heartbeat.ts deleted file mode 100644 index c1e7038..0000000 --- a/src/channel/simplex-heartbeat.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; -import { resolveSimplexAccount } from "../config/accounts.js"; -import type { SimplexClientRegistry } from "./simplex-client-registry.js"; -import { parseSimplexAllowlistEntry } from "./simplex-security.js"; - -function normalizeHeartbeatRecipient(raw: string): string { - const trimmed = raw.trim(); - if (!trimmed) { - return trimmed; - } - if (trimmed.startsWith("@") || trimmed.startsWith("#")) { - return trimmed; - } - return `@${trimmed}`; -} - -function collectHeartbeatRecipients(entries: Array | undefined): string[] { - const recipients = new Set(); - for (const raw of entries ?? []) { - const parsed = parseSimplexAllowlistEntry(raw); - if (!parsed || parsed.kind === "any") { - continue; - } - recipients.add(parsed.kind === "group" ? `#${parsed.value}` : `@${parsed.value}`); - } - return [...recipients]; -} - -export function buildSimplexHeartbeat( - registry: SimplexClientRegistry -): NonNullable { - return { - checkReady: async ({ cfg, accountId }) => { - const account = resolveSimplexAccount({ cfg, accountId }); - if (!account.enabled) { - return { ok: false as const, reason: "simplex-disabled" as const }; - } - if (!account.configured) { - return { ok: false as const, reason: "simplex-not-configured" as const }; - } - if (!registry.has(account.accountId)) { - return { ok: false as const, reason: "simplex-not-running" as const }; - } - return { ok: true as const, reason: "ok" as const }; - }, - resolveRecipients: ({ cfg, opts }) => { - if (opts?.to?.trim()) { - return { - recipients: [normalizeHeartbeatRecipient(opts.to)], - source: "flag", - }; - } - - const account = resolveSimplexAccount({ cfg, accountId: opts?.accountId }); - const dmRecipients = collectHeartbeatRecipients(account.config.allowFrom); - const groupRecipients = collectHeartbeatRecipients(account.config.groupAllowFrom); - - if (opts?.all) { - return { - recipients: [...new Set([...dmRecipients, ...groupRecipients])], - source: "all", - }; - } - - return { - recipients: dmRecipients.length > 0 ? dmRecipients : groupRecipients, - source: dmRecipients.length > 0 ? "allowFrom" : "groupAllowFrom", - }; - }, - }; -} diff --git a/src/channel/simplex-monitor.ts b/src/channel/simplex-monitor.ts deleted file mode 100644 index ba71760..0000000 --- a/src/channel/simplex-monitor.ts +++ /dev/null @@ -1,840 +0,0 @@ -import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract"; -import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound"; -import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import { - buildCancelFileCommand, - buildReceiveFileCommand, - buildSendMessagesCommand, - formatChatRef, -} from "../simplex/simplex-commands.js"; -import { resolveSimplexCommandError } from "../simplex/simplex-errors.js"; -import { SimplexWsClient, type SimplexWsEvent } from "../simplex/simplex-ws-client.js"; -import { getSimplexRuntime } from "./runtime.js"; -import { stripSimplexPrefix } from "./simplex-common.js"; -import { buildComposedMessages, resolveSimplexMediaMaxBytes } from "./simplex-media.js"; -import { isSimplexAllowlisted } from "./simplex-security.js"; - -export type SimplexMonitorOpts = { - account: ResolvedSimplexAccount; - cfg: OpenClawConfig; - runtime: RuntimeEnv; - abortSignal: AbortSignal; - statusSink?: (patch: Partial) => void; -}; - -type SimplexChatItem = { - chatInfo?: { - type?: string; - contact?: { contactId?: number; localDisplayName?: string; profile?: { displayName?: string } }; - groupInfo?: { groupId?: number; localDisplayName?: string }; - }; - chatItem?: { - chatDir?: { - type?: string; - groupMember?: { - memberId?: string; - groupMemberId?: number; - contactId?: number | string; - localDisplayName?: string; - }; - }; - meta?: { itemId?: number; itemTs?: string }; - content?: { type?: string; msgContent?: { type?: string; text?: string } }; - file?: { - fileId?: number; - fileName?: string; - fileSize?: number; - fileSource?: { filePath?: string }; - }; - }; -}; - -const INBOUND_DIRS = new Set(["directRcv", "groupRcv"]); -const PENDING_FILE_TIMEOUT_MS = 90_000; - -type PendingInboundFile = { - fileId: number; - ctxPayload: Record; - storePath: string; - sessionKey: string; - chatRef: string; - rawBody: string; - account: ResolvedSimplexAccount; - cfg: OpenClawConfig; - runtime: RuntimeEnv; - client: SimplexWsClient; - statusSink?: (patch: Partial) => void; -}; - -const pendingFiles = new Map(); - -function pendingKey(accountId: string, fileId: number): string { - return `${accountId}:${fileId}`; -} - -function normalizeSimplexSenderId(value?: string | null): string | undefined { - let trimmed = value?.trim(); - if (!trimmed) { - return undefined; - } - trimmed = stripSimplexPrefix(trimmed); - if (!trimmed) { - return undefined; - } - if (trimmed.startsWith("@")) { - trimmed = trimmed.slice(1).trim(); - } else { - const kindLower = trimmed.toLowerCase(); - if (kindLower.startsWith("contact:")) { - trimmed = trimmed.slice("contact:".length).trim(); - } else if (kindLower.startsWith("user:")) { - trimmed = trimmed.slice("user:".length).trim(); - } else if (kindLower.startsWith("member:")) { - trimmed = trimmed.slice("member:".length).trim(); - } - } - return trimmed || undefined; -} - -function resolveMessageText( - content: { type?: string; text?: string } | undefined, - fileName?: string -): string { - if (!content) { - return ""; - } - const text = content.text?.trim() ?? ""; - if (text) { - return text; - } - switch (content.type) { - case "image": - return "[image]"; - case "video": - return "[video]"; - case "voice": - return "[voice message]"; - case "file": - return fileName ? `[file: ${fileName}]` : "[file]"; - case "link": - return "[link]"; - case "report": - return "[report]"; - case "chat": - return "[chat]"; - default: - return "[message]"; - } -} - -function isInboundChatItem(item: SimplexChatItem): boolean { - const dir = item.chatItem?.chatDir?.type; - return Boolean(dir && INBOUND_DIRS.has(dir)); -} - -function resolveChatContext(item: SimplexChatItem): { - chatType: "direct" | "group"; - chatId: number; - chatLabel: string; - senderId?: string; - senderName?: string; -} | null { - const info = item.chatInfo; - if (!info || !item.chatItem) { - return null; - } - if (info.type === "direct") { - const contactId = info.contact?.contactId; - if (typeof contactId !== "number") { - return null; - } - const senderName = - info.contact?.localDisplayName?.trim() || - info.contact?.profile?.displayName?.trim() || - undefined; - return { - chatType: "direct", - chatId: contactId, - chatLabel: senderName || `contact:${contactId}`, - senderId: String(contactId), - senderName, - }; - } - if (info.type === "group") { - const groupId = info.groupInfo?.groupId; - if (typeof groupId !== "number") { - return null; - } - const member = item.chatItem?.chatDir?.groupMember; - const contactId = - typeof member?.contactId === "number" - ? String(member.contactId) - : member?.contactId?.trim() || undefined; - const senderId = - contactId ?? - member?.memberId?.trim() ?? - (typeof member?.groupMemberId === "number" ? String(member.groupMemberId) : undefined); - const senderName = member?.localDisplayName?.trim() || undefined; - const groupLabel = info.groupInfo?.localDisplayName?.trim() || `group:${groupId}`; - return { - chatType: "group", - chatId: groupId, - chatLabel: groupLabel, - senderId: senderId || undefined, - senderName, - }; - } - return null; -} - -function resolveSimplexGroupRequireMention(params: { - account: ResolvedSimplexAccount; - groupId?: number | null; -}): boolean { - const groupId = params.groupId ? String(params.groupId) : undefined; - const groups = params.account.config.groups ?? {}; - const entry = groupId ? groups[groupId] : undefined; - const fallback = groups["*"]; - if (typeof entry?.requireMention === "boolean") { - return entry.requireMention; - } - if (typeof fallback?.requireMention === "boolean") { - return fallback.requireMention; - } - return true; -} - -async function sendSimplexPayload(params: { - client: SimplexWsClient; - chatRef: string; - cfg: OpenClawConfig; - accountId: string; - payload: { - text?: string; - mediaUrl?: string; - mediaUrls?: string[]; - audioAsVoice?: boolean; - }; -}): Promise<{ messageId?: number }> { - const composedMessages = await buildComposedMessages({ - cfg: params.cfg, - accountId: params.accountId, - text: params.payload.text, - mediaUrl: params.payload.mediaUrl, - mediaUrls: params.payload.mediaUrls, - audioAsVoice: params.payload.audioAsVoice, - }); - if (composedMessages.length === 0) { - return {}; - } - const cmd = buildSendMessagesCommand({ - chatRef: params.chatRef, - composedMessages, - }); - const response = await params.client.sendCommand(cmd); - const resp = response.resp as { - type?: string; - chatError?: { errorType?: { type?: string; message?: string } }; - chatItems?: Array<{ chatItem?: { meta?: { itemId?: number } } }>; - }; - const commandError = resolveSimplexCommandError(resp); - if (commandError) { - throw new Error(commandError); - } - if (resp?.type === "newChatItems") { - const itemId = resp.chatItems?.[0]?.chatItem?.meta?.itemId; - return { messageId: typeof itemId === "number" ? itemId : undefined }; - } - return {}; -} - -export async function startSimplexMonitor(params: SimplexMonitorOpts): Promise<{ - client: SimplexWsClient; -}> { - const { account, cfg, runtime, statusSink } = params; - const client = new SimplexWsClient({ - url: account.wsUrl, - connectTimeoutMs: account.config.connection?.connectTimeoutMs, - logger: { - info: (message) => runtime?.log?.(message), - warn: (message) => runtime?.error?.(message), - error: (message) => runtime?.error?.(message), - }, - }); - - const stopConnectionState = client.onConnectionState((state) => { - if (state.connected) { - statusSink?.({ - connected: true, - running: true, - lastConnectedAt: state.at, - lastDisconnect: null, - lastError: null, - healthState: "healthy", - }); - return; - } - - statusSink?.({ - connected: false, - running: state.expected ? false : undefined, - lastDisconnect: state.error ? { at: state.at, error: state.error } : { at: state.at }, - ...(state.error ? { lastError: state.error } : {}), - healthState: state.expected ? "stopped" : "disconnected", - }); - }); - - await connectWithRetry({ - client, - runtime, - accountId: account.accountId, - abortSignal: params.abortSignal, - }); - - const stopListening = client.onEvent(async (event) => { - try { - await handleSimplexEvent({ event, account, cfg, runtime, statusSink, client }); - } catch (err) { - runtime.error?.(`[${account.accountId}] SimpleX event error: ${String(err)}`); - } - }); - - params.abortSignal.addEventListener( - "abort", - () => { - stopConnectionState(); - stopListening(); - client.close().catch((err) => { - runtime.error?.(`[${account.accountId}] SimpleX close failed: ${String(err)}`); - }); - }, - { once: true } - ); - - return { client }; -} - -async function connectWithRetry(params: { - client: SimplexWsClient; - runtime: RuntimeEnv; - accountId: string; - abortSignal: AbortSignal; - attempts?: number; - baseDelayMs?: number; - maxDelayMs?: number; -}): Promise { - const attempts = params.attempts ?? 10; - let delayMs = params.baseDelayMs ?? 500; - const maxDelayMs = params.maxDelayMs ?? 5_000; - - for (let attempt = 1; attempt <= attempts; attempt += 1) { - if (params.abortSignal.aborted) { - throw new Error("SimpleX connect aborted"); - } - try { - await params.client.connect(); - return; - } catch (err) { - if (attempt >= attempts) { - throw err; - } - params.runtime.error?.( - `[${params.accountId}] SimpleX connect failed (attempt ${attempt}/${attempts}): ${String(err)}; retrying in ${delayMs}ms` - ); - await sleep(delayMs, params.abortSignal); - delayMs = Math.min(maxDelayMs, delayMs * 2); - } - } -} - -function sleep(ms: number, abortSignal: AbortSignal): Promise { - if (abortSignal.aborted) { - return Promise.reject(new Error("SimpleX connect aborted")); - } - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - cleanup(); - resolve(); - }, ms); - const onAbort = () => { - cleanup(); - reject(new Error("SimpleX connect aborted")); - }; - const cleanup = () => { - clearTimeout(timer); - abortSignal.removeEventListener("abort", onAbort); - }; - abortSignal.addEventListener("abort", onAbort, { once: true }); - }); -} - -async function handleSimplexEvent(params: { - event: SimplexWsEvent; - account: ResolvedSimplexAccount; - cfg: OpenClawConfig; - runtime: RuntimeEnv; - statusSink?: (patch: Partial) => void; - client: SimplexWsClient; -}): Promise { - const { event, account, cfg, runtime, statusSink, client } = params; - statusSink?.({ lastEventAt: Date.now() }); - if (event.type === "rcvFileDescrReady") { - const fileId = Number( - (event as { rcvFileTransfer?: { fileId?: number } })?.rcvFileTransfer?.fileId - ); - if (Number.isFinite(fileId)) { - await requestFileDownload({ fileId, account, client, runtime }); - } - return; - } - - if (event.type === "rcvFileComplete") { - const chatItem = (event as { chatItem?: SimplexChatItem })?.chatItem; - const file = chatItem?.chatItem?.file; - const fileId = typeof file?.fileId === "number" ? file.fileId : null; - if (fileId && pendingFiles.has(pendingKey(account.accountId, fileId))) { - const filePath = file?.fileSource?.filePath?.trim(); - await finalizePendingFile({ - accountId: account.accountId, - fileId, - filePath, - }); - } - return; - } - - if (event.type !== "newChatItems") { - return; - } - - const chatItems = event.chatItems; - const items = Array.isArray(chatItems) ? (chatItems as SimplexChatItem[]) : []; - - for (const item of items) { - if (!isInboundChatItem(item)) { - continue; - } - - const context = resolveChatContext(item); - if (!context) { - continue; - } - - const content = - item.chatItem?.content?.type === "rcvMsgContent" - ? item.chatItem?.content?.msgContent - : undefined; - - if (!content) { - continue; - } - - const rawBody = resolveMessageText(content, item.chatItem?.file?.fileName); - if (!rawBody) { - continue; - } - - const normalizedSenderId = normalizeSimplexSenderId(context.senderId); - const dmPeerId = normalizedSenderId ?? String(context.chatId); - const chatRef = formatChatRef({ - type: context.chatType, - id: context.chatType === "group" ? context.chatId : dmPeerId, - }); - - const core = getSimplexRuntime(); - - const route = core.channel.routing.resolveAgentRoute({ - cfg, - channel: SIMPLEX_CHANNEL_ID, - accountId: account.accountId, - peer: { - kind: context.chatType === "group" ? "group" : "direct", - id: context.chatType === "group" ? String(context.chatId) : dmPeerId, - }, - }); - - const isGroup = context.chatType === "group"; - const dmPolicy = account.config.dmPolicy ?? "pairing"; - const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy; - const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist"; - const configAllowFrom = (account.config.allowFrom ?? []).map((entry) => String(entry)); - const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((entry) => - String(entry) - ); - const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, cfg); - const shouldLoadAllowFromStore = - (!isGroup && (dmPolicy !== "open" || shouldComputeAuth)) || - (isGroup && (groupPolicy !== "open" || shouldComputeAuth)); - const storeAllowFrom = shouldLoadAllowFromStore - ? await core.channel.pairing - .readAllowFromStore({ - channel: SIMPLEX_CHANNEL_ID, - accountId: account.accountId, - }) - .catch(() => []) - : []; - const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom]; - const baseGroupAllowFrom = - configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom; - const effectiveGroupAllowFrom = [...baseGroupAllowFrom, ...storeAllowFrom]; - const allowlistForCommands = isGroup ? effectiveGroupAllowFrom : effectiveDmAllowFrom; - const senderAllowedForCommands = isSimplexAllowlisted({ - allowFrom: allowlistForCommands, - senderId: normalizedSenderId, - groupId: String(context.chatId), - allowGroupId: isGroup, - }); - const useAccessGroups = cfg.commands?.useAccessGroups !== false; - const commandAuthorized = shouldComputeAuth - ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({ - useAccessGroups, - authorizers: [ - { - configured: allowlistForCommands.length > 0, - allowed: senderAllowedForCommands, - }, - ], - }) - : undefined; - - if (isGroup) { - if (groupPolicy === "disabled") { - runtime.log?.(`[${account.accountId}] SimpleX drop group (groupPolicy=disabled)`); - continue; - } - if (groupPolicy === "allowlist") { - if (effectiveGroupAllowFrom.length === 0) { - runtime.log?.( - `[${account.accountId}] SimpleX drop group (groupPolicy=allowlist, empty allowlist)` - ); - continue; - } - const allowed = isSimplexAllowlisted({ - allowFrom: effectiveGroupAllowFrom, - senderId: normalizedSenderId, - groupId: String(context.chatId), - allowGroupId: true, - }); - if (!allowed) { - runtime.log?.( - `[${account.accountId}] SimpleX drop group sender ${context.senderId ?? "unknown"} (not allowlisted)` - ); - continue; - } - } - } else { - if (dmPolicy !== "open") { - const allowed = isSimplexAllowlisted({ - allowFrom: effectiveDmAllowFrom, - senderId: normalizedSenderId, - allowGroupId: false, - }); - if (!allowed) { - if (dmPolicy === "pairing") { - const senderId = normalizedSenderId ?? String(context.chatId); - const { code, created } = await core.channel.pairing.upsertPairingRequest({ - channel: SIMPLEX_CHANNEL_ID, - id: senderId, - accountId: account.accountId, - meta: { name: context.senderName }, - }); - if (created) { - runtime.log?.(`[${account.accountId}] SimpleX pairing request sender=${senderId}`); - try { - await sendSimplexPayload({ - client, - chatRef, - cfg, - accountId: account.accountId, - payload: { - text: core.channel.pairing.buildPairingReply({ - channel: SIMPLEX_CHANNEL_ID, - idLine: `Your SimpleX contact id: ${senderId}`, - code, - }), - }, - }); - statusSink?.({ lastOutboundAt: Date.now() }); - } catch (err) { - runtime.error?.( - `[${account.accountId}] SimpleX pairing reply failed: ${String(err)}` - ); - } - } - } else { - runtime.log?.( - `[${account.accountId}] SimpleX drop DM from ${context.senderId ?? "unknown"} (dmPolicy=${dmPolicy})` - ); - } - continue; - } - } - } - - let effectiveWasMentioned: boolean | undefined; - if (isGroup) { - const requireMention = resolveSimplexGroupRequireMention({ - account, - groupId: context.chatId, - }); - const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId); - const wasMentioned = core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes); - const allowTextCommands = core.channel.commands.shouldHandleTextCommands({ - cfg, - surface: SIMPLEX_CHANNEL_ID, - }); - const mentionGate = resolveInboundMentionDecision({ - facts: { - canDetectMention: mentionRegexes.length > 0, - wasMentioned, - }, - policy: { - isGroup: true, - requireMention, - allowTextCommands, - hasControlCommand: core.channel.text.hasControlCommand(rawBody, cfg), - commandAuthorized: commandAuthorized === true, - }, - }); - effectiveWasMentioned = mentionGate.effectiveWasMentioned; - if (mentionGate.shouldSkip) { - runtime.log?.( - `[${account.accountId}] SimpleX drop group ${context.chatId} (mention required)` - ); - continue; - } - } - - if (isGroup && core.channel.commands.isControlCommandMessage(rawBody, cfg)) { - if (commandAuthorized !== true) { - runtime.log?.( - `[${account.accountId}] SimpleX drop control command from ${context.senderId ?? "unknown"}` - ); - continue; - } - } - - const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { - agentId: route.agentId, - }); - - const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg); - const previousTimestamp = core.channel.session.readSessionUpdatedAt({ - storePath, - sessionKey: route.sessionKey, - }); - - const fromLabel = - context.chatType === "group" - ? `group:${context.chatId}` - : context.senderName || `contact:${context.senderId ?? "unknown"}`; - - const body = core.channel.reply.formatAgentEnvelope({ - channel: "SimpleX", - from: fromLabel, - previousTimestamp, - envelope: envelopeOptions, - body: rawBody, - }); - - const ctxPayload = core.channel.reply.finalizeInboundContext({ - Body: body, - RawBody: rawBody, - CommandBody: rawBody, - From: - context.chatType === "group" - ? `${SIMPLEX_CHANNEL_ID}:group:${context.chatId}` - : `${SIMPLEX_CHANNEL_ID}:${dmPeerId}`, - To: - context.chatType === "group" - ? `${SIMPLEX_CHANNEL_ID}:group:${context.chatId}` - : `${SIMPLEX_CHANNEL_ID}:${dmPeerId}`, - SessionKey: route.sessionKey, - AccountId: route.accountId, - ChatType: context.chatType === "group" ? "group" : "direct", - ConversationLabel: fromLabel, - GroupSubject: context.chatType === "group" ? context.chatLabel : undefined, - SenderName: context.senderName, - SenderId: context.senderId, - Provider: SIMPLEX_CHANNEL_ID, - Surface: SIMPLEX_CHANNEL_ID, - MessageSid: - typeof item.chatItem?.meta?.itemId === "number" - ? String(item.chatItem.meta.itemId) - : undefined, - WasMentioned: context.chatType === "group" ? effectiveWasMentioned : undefined, - CommandAuthorized: commandAuthorized, - OriginatingChannel: SIMPLEX_CHANNEL_ID, - OriginatingTo: - context.chatType === "group" - ? `${SIMPLEX_CHANNEL_ID}:group:${context.chatId}` - : `${SIMPLEX_CHANNEL_ID}:${dmPeerId}`, - }); - - const fileId = item.chatItem?.file?.fileId; - const fileSize = item.chatItem?.file?.fileSize; - const maxBytes = resolveSimplexMediaMaxBytes({ - cfg, - accountId: account.accountId, - }); - - const pending: PendingInboundFile = { - fileId: typeof fileId === "number" ? fileId : -1, - ctxPayload, - storePath, - sessionKey: route.sessionKey, - chatRef, - rawBody, - account, - cfg, - runtime, - client, - statusSink, - }; - - if (typeof fileId === "number") { - if (typeof fileSize === "number" && fileSize > maxBytes) { - runtime.error?.( - `[${account.accountId}] SimpleX file ${fileId} exceeds limit (${fileSize} > ${maxBytes})` - ); - continue; - } - const accepted = await requestFileDownload({ fileId, account, client, runtime }); - if (accepted) { - pendingFiles.set(pendingKey(account.accountId, fileId), pending); - setTimeout(() => { - const key = pendingKey(account.accountId, fileId); - const current = pendingFiles.get(key); - if (current) { - pendingFiles.delete(key); - void current.client.sendCommand(buildCancelFileCommand(fileId)).catch((err) => { - runtime.error?.( - `[${account.accountId}] SimpleX file timeout cancel failed: ${String(err)}` - ); - }); - void dispatchInbound({ - pending: current, - mediaPath: undefined, - mediaType: undefined, - }).catch((err) => { - runtime.error?.( - `[${account.accountId}] SimpleX pending file timeout: ${String(err)}` - ); - }); - } - }, PENDING_FILE_TIMEOUT_MS); - continue; - } - } - - await dispatchInbound({ pending, mediaPath: undefined, mediaType: undefined }); - } -} - -async function requestFileDownload(params: { - fileId: number; - account: ResolvedSimplexAccount; - client: SimplexWsClient; - runtime: RuntimeEnv; -}): Promise { - const { fileId, account, client, runtime } = params; - const autoAccept = account.config.connection?.autoAcceptFiles !== false; - if (!autoAccept) { - return false; - } - const cmd = buildReceiveFileCommand({ fileId }); - try { - await client.sendCommand(cmd); - } catch (err) { - runtime.error?.(`[${account.accountId}] SimpleX receive file failed: ${String(err)}`); - return false; - } - return true; -} - -async function finalizePendingFile(params: { - accountId: string; - fileId: number; - filePath?: string; -}): Promise { - const pending = pendingFiles.get(pendingKey(params.accountId, params.fileId)); - if (!pending) { - return; - } - pendingFiles.delete(pendingKey(params.accountId, params.fileId)); - const mediaPath = params.filePath?.trim() || undefined; - let mediaType: string | undefined; - if (mediaPath) { - mediaType = await getSimplexRuntime().media.detectMime({ filePath: mediaPath }); - } - await dispatchInbound({ pending, mediaPath, mediaType }); -} - -async function dispatchInbound(params: { - pending: PendingInboundFile; - mediaPath?: string; - mediaType?: string; -}): Promise { - const { pending, mediaPath, mediaType } = params; - const core = getSimplexRuntime(); - const ctxPayload = { - ...pending.ctxPayload, - MediaPath: mediaPath, - MediaType: mediaType, - MediaUrl: mediaPath, - }; - - await core.channel.session.recordInboundSession({ - storePath: pending.storePath, - sessionKey: (ctxPayload as { SessionKey?: string }).SessionKey ?? pending.sessionKey, - ctx: ctxPayload, - onRecordError: (err) => { - pending.runtime.error?.(`simplex: failed updating session meta: ${String(err)}`); - }, - }); - - pending.statusSink?.({ lastInboundAt: Date.now() }); - - await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({ - ctx: ctxPayload, - cfg: pending.cfg, - dispatcherOptions: { - deliver: async (payload) => { - const hasMedia = - Boolean(payload.mediaUrl) || - (Array.isArray(payload.mediaUrls) && payload.mediaUrls.length > 0); - if (!payload.text && !hasMedia) { - return; - } - if (!pending.account.enabled || !pending.account.configured) { - pending.runtime.error?.( - `[${pending.account.accountId}] SimpleX reply skipped: account not ready (enabled=${pending.account.enabled}, configured=${pending.account.configured})` - ); - return; - } - await sendSimplexPayload({ - client: pending.client, - chatRef: pending.chatRef, - cfg: pending.cfg, - accountId: pending.account.accountId, - payload, - }); - pending.statusSink?.({ lastOutboundAt: Date.now() }); - }, - onError: (err) => { - pending.runtime.error?.( - `[${pending.account.accountId}] SimpleX reply failed: ${String(err)}` - ); - }, - }, - replyOptions: { - disableBlockStreaming: - typeof pending.account.config.blockStreaming === "boolean" - ? !pending.account.config.blockStreaming - : undefined, - }, - }); -} diff --git a/src/channel/simplex-send.ts b/src/channel/simplex-send.ts deleted file mode 100644 index 0664ed3..0000000 --- a/src/channel/simplex-send.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { - buildSendMessagesCommand, - type SimplexComposedMessage, -} from "../simplex/simplex-commands.js"; -import { resolveSimplexCommandError } from "../simplex/simplex-errors.js"; -import type { SimplexClientRegistry } from "./simplex-client-registry.js"; -import { withSimplexRegistryClient } from "./simplex-client-registry.js"; -import { normalizeSimplexMessageId } from "./simplex-common.js"; -import { buildComposedMessages } from "./simplex-media.js"; - -export async function sendComposedMessages(params: { - registry: SimplexClientRegistry; - account: ResolvedSimplexAccount; - chatRef: string; - composedMessages: SimplexComposedMessage[]; -}): Promise<{ messageId?: string }> { - if (params.composedMessages.length === 0) { - return {}; - } - const cmd = buildSendMessagesCommand({ - chatRef: params.chatRef, - composedMessages: params.composedMessages, - }); - const response = await withSimplexRegistryClient(params.registry, params.account, (client) => - client.sendCommand(cmd) - ); - const resp = response.resp as { - type?: string; - chatError?: { errorType?: { type?: string; message?: string } }; - chatItems?: Array<{ chatItem?: { meta?: { itemId?: unknown } } }>; - itemId?: unknown; - messageId?: unknown; - }; - const commandError = resolveSimplexCommandError(resp); - if (commandError) { - throw new Error(commandError); - } - const messageId = - normalizeSimplexMessageId(resp.chatItems?.[0]?.chatItem?.meta?.itemId) ?? - normalizeSimplexMessageId(resp.messageId) ?? - normalizeSimplexMessageId(resp.itemId); - return { messageId }; -} - -export async function buildAndSendSimplexMessages(params: { - registry: SimplexClientRegistry; - cfg: OpenClawConfig; - account: ResolvedSimplexAccount; - chatRef: string; - text?: string; - mediaUrl?: string; - mediaUrls?: string[]; - audioAsVoice?: boolean; -}): Promise<{ messageId?: string }> { - const composedMessages = await buildComposedMessages({ - cfg: params.cfg, - accountId: params.account.accountId, - text: params.text, - mediaUrl: params.mediaUrl, - mediaUrls: params.mediaUrls, - audioAsVoice: params.audioAsVoice, - }); - return await sendComposedMessages({ - registry: params.registry, - account: params.account, - chatRef: params.chatRef, - composedMessages, - }); -} diff --git a/src/channel/simplex-status.ts b/src/channel/simplex-status.ts deleted file mode 100644 index 3fcb19d..0000000 --- a/src/channel/simplex-status.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import type { SimplexClientRegistry } from "./simplex-client-registry.js"; -import { - DEFAULT_ACCOUNT_ID, - extractSimplexWsUrlFromApplication, - resolveSimplexHealthState, -} from "./simplex-common.js"; - -export function buildSimplexStatus( - _registry: SimplexClientRegistry -): NonNullable["status"]> { - return { - defaultRuntime: { - accountId: DEFAULT_ACCOUNT_ID, - running: false, - lastStartAt: null, - lastStopAt: null, - lastError: null, - }, - collectStatusIssues: (accounts) => - accounts.flatMap((account) => { - const lastError = typeof account.lastError === "string" ? account.lastError.trim() : ""; - if (!lastError) { - return []; - } - return [ - { - channel: SIMPLEX_CHANNEL_ID, - accountId: account.accountId, - kind: "runtime" as const, - message: `Channel error: ${lastError}`, - }, - ]; - }), - buildChannelSummary: ({ snapshot, account }) => ({ - configured: snapshot.configured ?? false, - running: snapshot.running ?? false, - connected: snapshot.connected ?? false, - lastStartAt: snapshot.lastStartAt ?? null, - lastStopAt: snapshot.lastStopAt ?? null, - lastError: snapshot.lastError ?? null, - lastConnectedAt: snapshot.lastConnectedAt ?? null, - lastDisconnect: snapshot.lastDisconnect ?? null, - lastEventAt: snapshot.lastEventAt ?? null, - healthState: resolveSimplexHealthState({ - configured: snapshot.configured ?? false, - running: snapshot.running ?? false, - connected: snapshot.connected ?? false, - lastError: snapshot.lastError ?? null, - }), - mode: snapshot.mode ?? null, - wsUrl: extractSimplexWsUrlFromApplication(snapshot.application) ?? account.wsUrl ?? null, - }), - buildAccountSnapshot: async ({ account, runtime }) => { - const wsUrl = extractSimplexWsUrlFromApplication(runtime?.application) ?? account.wsUrl; - return { - accountId: account.accountId, - name: account.name, - enabled: account.enabled, - configured: account.configured, - running: runtime?.running ?? false, - connected: runtime?.connected ?? false, - lastStartAt: runtime?.lastStartAt ?? null, - lastStopAt: runtime?.lastStopAt ?? null, - lastConnectedAt: runtime?.lastConnectedAt ?? null, - lastDisconnect: runtime?.lastDisconnect ?? null, - lastError: runtime?.lastError ?? null, - healthState: resolveSimplexHealthState({ - configured: account.configured, - running: runtime?.running ?? false, - connected: runtime?.connected ?? false, - lastError: runtime?.lastError ?? null, - }), - mode: runtime?.mode ?? account.mode, - lastInboundAt: runtime?.lastInboundAt ?? null, - lastOutboundAt: runtime?.lastOutboundAt ?? null, - lastEventAt: runtime?.lastEventAt ?? null, - application: { - wsUrl, - }, - }; - }, - }; -} diff --git a/src/channel/transport/simplex-connect.test.ts b/src/channel/transport/simplex-connect.test.ts new file mode 100644 index 0000000..3465dc8 --- /dev/null +++ b/src/channel/transport/simplex-connect.test.ts @@ -0,0 +1,96 @@ +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import { describe, expect, it, vi } from "vitest"; +import type { SimplexClient } from "../../simplex/runtime/client.js"; +import { connectSimplexWithRetry } from "./simplex-connect.js"; + +describe("connectSimplexWithRetry", () => { + it("keeps retrying by default until the SimpleX runtime becomes reachable", async () => { + const messages: string[] = []; + let attempts = 0; + const client = { + async connect() { + attempts += 1; + if (attempts < 3) { + throw new Error("runtime unavailable"); + } + }, + } as unknown as SimplexClient; + + await connectSimplexWithRetry({ + client, + runtime: { + log() {}, + error: (message: string) => messages.push(message), + exit() { + throw new Error("unexpected exit"); + }, + } as unknown as RuntimeEnv, + accountId: "default", + abortSignal: new AbortController().signal, + baseDelayMs: 0, + maxDelayMs: 0, + }); + + expect(attempts).toBe(3); + expect(messages).toEqual([ + "[default] SimpleX connect failed (attempt 1/unbounded): Error: runtime unavailable; retrying in 0ms", + "[default] SimpleX connect failed (attempt 2/unbounded): Error: runtime unavailable; retrying in 0ms", + ]); + }); + + it("still honors an explicit finite attempt budget", async () => { + const client = { + async connect() { + throw new Error("runtime unavailable"); + }, + } as unknown as SimplexClient; + + await expect( + connectSimplexWithRetry({ + client, + runtime: { + log() {}, + error() {}, + exit() { + throw new Error("unexpected exit"); + }, + } as unknown as RuntimeEnv, + accountId: "default", + abortSignal: new AbortController().signal, + attempts: 2, + baseDelayMs: 0, + maxDelayMs: 0, + }) + ).rejects.toThrow("runtime unavailable"); + }); + + it("stops immediately when abort happens during a failed connect attempt", async () => { + const abortController = new AbortController(); + const error = vi.fn(); + const client = { + async connect() { + abortController.abort(); + throw new Error("runtime unavailable"); + }, + } as unknown as SimplexClient; + + await expect( + connectSimplexWithRetry({ + client, + runtime: { + log() {}, + error, + exit() { + throw new Error("unexpected exit"); + }, + } as unknown as RuntimeEnv, + accountId: "default", + abortSignal: abortController.signal, + baseDelayMs: 0, + maxDelayMs: 0, + }) + ).rejects.toThrow("SimpleX connect aborted"); + + expect(error).not.toHaveBeenCalled(); + }); +}); diff --git a/src/channel/transport/simplex-connect.ts b/src/channel/transport/simplex-connect.ts new file mode 100644 index 0000000..48c50c3 --- /dev/null +++ b/src/channel/transport/simplex-connect.ts @@ -0,0 +1,60 @@ +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import type { SimplexClient } from "../../simplex/runtime/client.js"; + +export async function connectSimplexWithRetry(params: { + client: SimplexClient; + runtime: RuntimeEnv; + accountId: string; + abortSignal: AbortSignal; + attempts?: number; + baseDelayMs?: number; + maxDelayMs?: number; +}): Promise { + const attempts = params.attempts ?? Number.POSITIVE_INFINITY; + let delayMs = params.baseDelayMs ?? 500; + const maxDelayMs = params.maxDelayMs ?? 5_000; + const attemptsLabel = Number.isFinite(attempts) ? String(attempts) : "unbounded"; + + for (let attempt = 1; attempt <= attempts; attempt += 1) { + if (params.abortSignal.aborted) { + throw new Error("SimpleX connect aborted"); + } + try { + await params.client.connect(); + return; + } catch (err) { + if (params.abortSignal.aborted) { + throw new Error("SimpleX connect aborted"); + } + if (attempt >= attempts) { + throw err; + } + params.runtime.error?.( + `[${params.accountId}] SimpleX connect failed (attempt ${attempt}/${attemptsLabel}): ${String(err)}; retrying in ${delayMs}ms` + ); + await sleep(delayMs, params.abortSignal); + delayMs = Math.min(maxDelayMs, delayMs * 2); + } + } +} + +function sleep(ms: number, abortSignal: AbortSignal): Promise { + if (abortSignal.aborted) { + return Promise.reject(new Error("SimpleX connect aborted")); + } + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + cleanup(); + resolve(); + }, ms); + const onAbort = () => { + cleanup(); + reject(new Error("SimpleX connect aborted")); + }; + const cleanup = () => { + clearTimeout(timer); + abortSignal.removeEventListener("abort", onAbort); + }; + abortSignal.addEventListener("abort", onAbort, { once: true }); + }); +} diff --git a/src/cli/migration.ts b/src/cli/migration.ts new file mode 100644 index 0000000..c615b88 --- /dev/null +++ b/src/cli/migration.ts @@ -0,0 +1,395 @@ +import { access, mkdir, readdir, rename } from "node:fs/promises"; +import path from "node:path"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; +import { + LEGACY_SIMPLEX_CHANNEL_ID, + LEGACY_SIMPLEX_PLUGIN_ID, + SIMPLEX_CHANNEL_ID, + SIMPLEX_PLUGIN_ID, +} from "../constants.js"; + +const LEGACY_PLUGIN_ID = LEGACY_SIMPLEX_PLUGIN_ID; +const PLUGIN_ID = SIMPLEX_PLUGIN_ID; +const LEGACY_CHANNEL_ID = LEGACY_SIMPLEX_CHANNEL_ID; +const CHANNEL_ID = SIMPLEX_CHANNEL_ID; + +type MigrationResult = { + changed: string[]; + skipped: string[]; +}; + +type SimplexMigrationStateApi = { + runtime: { + state: { + resolveStateDir: () => string; + }; + }; +}; + +const CONNECTION_CONFIG_KEYS = new Set([ + "mode", + "wsUrl", + "wsHost", + "wsPort", + "allowUnsafeRemoteWs", + "autoAcceptFiles", + "connectTimeoutMs", +]); + +const LEGACY_RUNTIME_KEYS = new Set([ + "authToken", + "cliPath", + "command", + "dbFilePrefix", + "displayName", + "fullName", + "headers", + "host", + "httpUrl", + "managed", + "migrationConfirmation", + "mode", + "path", + "port", + "process", + "reconnect", + "retry", + "token", + "url", + "wsUrl", +]); + +function dedupeStrings(values: unknown): string[] | undefined { + if (!Array.isArray(values)) { + return undefined; + } + return [ + ...new Set( + values.filter( + (value): value is string => typeof value === "string" && value.trim().length > 0 + ) + ), + ]; +} + +function mergeObjects>( + legacy: T | undefined, + next: T | undefined +): T | undefined { + if (!legacy && !next) { + return undefined; + } + if (!legacy) { + return next; + } + if (!next) { + return legacy; + } + const merged: Record = { ...legacy, ...next }; + if ("connection" in legacy || "connection" in next) { + merged.connection = { + ...((legacy.connection as Record | undefined) ?? {}), + ...((next.connection as Record | undefined) ?? {}), + }; + } + if ("accounts" in legacy || "accounts" in next) { + merged.accounts = { + ...((legacy.accounts as Record | undefined) ?? {}), + ...((next.accounts as Record | undefined) ?? {}), + }; + } + return merged as T; +} + +function migrateConnectionConfig( + account: Record, + value: unknown, + pathLabel: string, + result: MigrationResult +): void { + if (value === undefined) { + return; + } + if (!value || typeof value !== "object" || Array.isArray(value)) { + result.changed.push( + `config: removed invalid ${pathLabel}; configure connection.wsUrl for the external WebSocket runtime` + ); + delete account.connection; + return; + } + + const connection = value as Record; + const nextConnection: Record = {}; + const moved: string[] = []; + const removed: string[] = []; + + for (const [key, fieldValue] of Object.entries(connection)) { + if (CONNECTION_CONFIG_KEYS.has(key)) { + nextConnection[key] = fieldValue; + } else if (key === "url" || key === "httpUrl") { + if (nextConnection.wsUrl === undefined && typeof fieldValue === "string") { + nextConnection.wsUrl = fieldValue; + moved.push(key); + } + } else if (key === "host") { + if (nextConnection.wsHost === undefined) { + nextConnection.wsHost = fieldValue; + moved.push(key); + } + } else if (key === "port") { + if (nextConnection.wsPort === undefined) { + nextConnection.wsPort = fieldValue; + moved.push(key); + } + } else { + removed.push(key); + } + } + + if (nextConnection.mode === undefined) { + nextConnection.mode = "external"; + } + account.connection = nextConnection; + + if (moved.length > 0) { + result.changed.push( + `config: moved runtime field(s) from ${pathLabel} to ${pathLabel.replace(/\.connection$/, "")}: ${[ + ...new Set(moved), + ] + .toSorted() + .join(", ")}` + ); + } + if (removed.length > 0) { + result.changed.push( + `config: removed legacy runtime field(s) from ${pathLabel}: ${removed.toSorted().join(", ")}` + ); + } +} + +function sanitizeSimplexAccountConfig( + value: unknown, + pathLabel: string, + result: MigrationResult +): Record | undefined { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return value as Record | undefined; + } + const account = { ...(value as Record) }; + const removed: string[] = []; + const connection = { + ...((account.connection as Record | undefined) ?? {}), + }; + + for (const key of Object.keys(account)) { + if (LEGACY_RUNTIME_KEYS.has(key)) { + const fieldValue = account[key]; + if (key === "wsUrl" || key === "url" || key === "httpUrl") { + connection.wsUrl ??= fieldValue; + } else if (key === "host") { + connection.wsHost ??= fieldValue; + } else if (key === "port") { + connection.wsPort ??= fieldValue; + } else if (key === "mode" && fieldValue === "external") { + connection.mode ??= "external"; + } + delete account[key]; + removed.push(key); + } + } + if (Object.keys(connection).length > 0) { + account.connection = connection; + } + if (removed.length > 0) { + result.changed.push( + `config: removed legacy runtime field(s) from ${pathLabel}: ${removed.toSorted().join(", ")}` + ); + } + + if ("connection" in account) { + migrateConnectionConfig(account, account.connection, `${pathLabel}.connection`, result); + } + + return account; +} + +function sanitizeSimplexChannelConfig(channel: unknown, result: MigrationResult): unknown { + if (!channel || typeof channel !== "object" || Array.isArray(channel)) { + return channel; + } + + const next = sanitizeSimplexAccountConfig(channel, `channels.${CHANNEL_ID}`, result) as Record< + string, + unknown + >; + const accounts = + next.accounts && typeof next.accounts === "object" && !Array.isArray(next.accounts) + ? (next.accounts as Record) + : null; + + if (accounts) { + const sanitizedAccounts: Record = {}; + for (const [accountId, account] of Object.entries(accounts)) { + sanitizedAccounts[accountId] = sanitizeSimplexAccountConfig( + account, + `channels.${CHANNEL_ID}.accounts.${accountId}`, + result + ); + } + next.accounts = sanitizedAccounts; + } + + return next; +} + +export function migrateConfigObject(rawConfig: Record): { + nextConfig: Record; + result: MigrationResult; +} { + const result: MigrationResult = { changed: [], skipped: [] }; + const nextConfig: Record = { + ...rawConfig, + plugins: { ...((rawConfig.plugins as Record | undefined) ?? {}) }, + channels: { ...((rawConfig.channels as Record | undefined) ?? {}) }, + }; + + const plugins = nextConfig.plugins as Record; + const channels = nextConfig.channels as Record; + + const entries = { ...((plugins.entries as Record | undefined) ?? {}) }; + if (LEGACY_PLUGIN_ID in entries) { + entries[PLUGIN_ID] = mergeObjects( + entries[LEGACY_PLUGIN_ID] as Record | undefined, + entries[PLUGIN_ID] as Record | undefined + ); + delete entries[LEGACY_PLUGIN_ID]; + plugins.entries = entries; + result.changed.push( + `config: plugins.entries.${LEGACY_PLUGIN_ID} -> plugins.entries.${PLUGIN_ID}` + ); + } + + const installs = { ...((plugins.installs as Record | undefined) ?? {}) }; + if (LEGACY_PLUGIN_ID in installs) { + installs[PLUGIN_ID] = mergeObjects( + installs[LEGACY_PLUGIN_ID] as Record | undefined, + installs[PLUGIN_ID] as Record | undefined + ); + delete installs[LEGACY_PLUGIN_ID]; + plugins.installs = installs; + result.changed.push( + `config: plugins.installs.${LEGACY_PLUGIN_ID} -> plugins.installs.${PLUGIN_ID}` + ); + } + + for (const key of ["allow", "deny"] as const) { + const values = dedupeStrings(plugins[key]); + if (!values?.includes(LEGACY_PLUGIN_ID)) { + continue; + } + const migrated = dedupeStrings( + values.map((value) => (value === LEGACY_PLUGIN_ID ? PLUGIN_ID : value)) + ); + plugins[key] = migrated; + result.changed.push( + `config: plugins.${key} replaced "${LEGACY_PLUGIN_ID}" with "${PLUGIN_ID}"` + ); + } + + if (LEGACY_CHANNEL_ID in channels) { + channels[CHANNEL_ID] = mergeObjects( + channels[LEGACY_CHANNEL_ID] as Record | undefined, + channels[CHANNEL_ID] as Record | undefined + ); + delete channels[LEGACY_CHANNEL_ID]; + result.changed.push(`config: channels.${LEGACY_CHANNEL_ID} -> channels.${CHANNEL_ID}`); + } + + if (CHANNEL_ID in channels) { + channels[CHANNEL_ID] = sanitizeSimplexChannelConfig(channels[CHANNEL_ID], result); + } + + return { nextConfig, result }; +} + +export async function migrateStateFiles( + api: SimplexMigrationStateApi, + dryRun: boolean +): Promise { + const result: MigrationResult = { changed: [], skipped: [] }; + const credentialsDir = path.join(api.runtime.state.resolveStateDir(), "credentials"); + await mkdir(credentialsDir, { recursive: true }); + const entries = await readdir(credentialsDir, { withFileTypes: true }); + + for (const entry of entries) { + if (!entry.isFile()) { + continue; + } + const source = entry.name; + let target: string | null = null; + if (source === `${LEGACY_CHANNEL_ID}-pairing.json`) { + target = `${CHANNEL_ID}-pairing.json`; + } else if (source === `${LEGACY_CHANNEL_ID}-allowFrom.json`) { + target = `${CHANNEL_ID}-allowFrom.json`; + } else { + const accountMatch = source.match(/^simplex-(.+)-allowFrom\.json$/); + if (accountMatch?.[1]) { + target = `${CHANNEL_ID}-${accountMatch[1]}-allowFrom.json`; + } + } + if (!target) { + continue; + } + const sourcePath = path.join(credentialsDir, source); + const targetPath = path.join(credentialsDir, target); + try { + await access(targetPath); + result.skipped.push(`state: skipped ${source} because ${target} already exists`); + continue; + } catch {} + if (!dryRun) { + await rename(sourcePath, targetPath); + } + result.changed.push(`state: ${source} -> ${target}`); + } + + return result; +} + +function printMigrationResult(result: MigrationResult, dryRun: boolean): void { + const title = dryRun + ? "OpenClaw SimpleX migration dry run" + : "OpenClaw SimpleX migration complete"; + console.log(title); + if (result.changed.length === 0) { + console.log("- No changes were needed."); + } else { + for (const line of result.changed) { + console.log(`- ${line}`); + } + } + for (const line of result.skipped) { + console.log(`- ${line}`); + } +} + +export async function runMigration(api: OpenClawPluginApi, dryRun: boolean): Promise { + const currentConfig = api.runtime.config.current() as OpenClawConfig; + const { nextConfig, result: configResult } = migrateConfigObject(currentConfig); + const stateResult = await migrateStateFiles(api, dryRun); + const result: MigrationResult = { + changed: [...configResult.changed, ...stateResult.changed], + skipped: [...configResult.skipped, ...stateResult.skipped], + }; + if (!dryRun && configResult.changed.length > 0) { + await api.runtime.config.replaceConfigFile({ + nextConfig: nextConfig as OpenClawConfig, + afterWrite: { + mode: "restart", + reason: "SimpleX migration updated plugin or channel configuration", + }, + }); + } + printMigrationResult(result, dryRun); +} diff --git a/src/cli/plugin-cli.test.ts b/src/cli/plugin-cli.test.ts index 7b4ee57..4232af0 100644 --- a/src/cli/plugin-cli.test.ts +++ b/src/cli/plugin-cli.test.ts @@ -1,16 +1,59 @@ import { mkdir, readdir, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; +import { describe, expect, it, vi } from "vitest"; +import { + LEGACY_SIMPLEX_CHANNEL_ID, + LEGACY_SIMPLEX_PLUGIN_ID, + SIMPLEX_CHANNEL_ID, + SIMPLEX_PLUGIN_ID, +} from "../constants.js"; +import { runMigration } from "./migration.js"; import { - CHANNEL_ID, - LEGACY_CHANNEL_ID, - LEGACY_PLUGIN_ID, migrateConfigObject, migrateStateFiles, - PLUGIN_ID, + registerSimplexCliMetadata, } from "./plugin-cli.js"; +const CHANNEL_ID = SIMPLEX_CHANNEL_ID; +const LEGACY_CHANNEL_ID = LEGACY_SIMPLEX_CHANNEL_ID; +const LEGACY_PLUGIN_ID = LEGACY_SIMPLEX_PLUGIN_ID; +const PLUGIN_ID = SIMPLEX_PLUGIN_ID; + +class FakeCliCommand { + constructor( + private readonly path: string[], + private readonly commands: string[][] + ) {} + + command(name: string): FakeCliCommand { + const commandPath = [...this.path, name]; + this.commands.push(commandPath); + return new FakeCliCommand(commandPath, this.commands); + } + + alias(): this { + return this; + } + + description(): this { + return this; + } + + option(): this { + return this; + } + + requiredOption(): this { + return this; + } + + action(): this { + return this; + } +} + describe("simplex migration config", () => { it("migrates legacy plugin and channel ids", () => { const { nextConfig, result } = migrateConfigObject({ @@ -27,7 +70,7 @@ describe("simplex migration config", () => { channels: { [LEGACY_CHANNEL_ID]: { enabled: true, - connection: { wsUrl: "ws://127.0.0.1:5225" }, + connection: {}, accounts: { ops: { allowFrom: ["*"], @@ -51,7 +94,9 @@ describe("simplex migration config", () => { channels: { [CHANNEL_ID]: { enabled: true, - connection: { wsUrl: "ws://127.0.0.1:5225" }, + connection: { + mode: "external", + }, accounts: { ops: { allowFrom: ["*"], @@ -70,6 +115,122 @@ describe("simplex migration config", () => { `config: channels.${LEGACY_CHANNEL_ID} -> channels.${CHANNEL_ID}` ); }); + + it("removes legacy WebSocket and CLI runtime config while preserving policies and accounts", () => { + const { nextConfig, result } = migrateConfigObject({ + channels: { + [LEGACY_CHANNEL_ID]: { + enabled: true, + wsUrl: "ws://127.0.0.1:5225", + managed: true, + cliPath: "/usr/local/bin/simplex-chat", + connection: { + wsUrl: "ws://127.0.0.1:5225", + authToken: "legacy-token", + dbFilePrefix: "~/.openclaw/simplex/kept", + connectTimeoutMs: 7000, + }, + dmPolicy: "pairing", + allowFrom: ["alice"], + accounts: { + ops: { + name: "Ops", + allowFrom: ["bob"], + groupAllowFrom: ["group:ops"], + connection: { + wsUrl: "ws://127.0.0.1:5226", + displayName: "Ops Bot", + }, + }, + }, + }, + }, + }); + + expect(nextConfig.channels).toEqual({ + [CHANNEL_ID]: { + enabled: true, + connection: { + wsUrl: "ws://127.0.0.1:5225", + connectTimeoutMs: 7000, + mode: "external", + }, + dmPolicy: "pairing", + allowFrom: ["alice"], + accounts: { + ops: { + name: "Ops", + allowFrom: ["bob"], + groupAllowFrom: ["group:ops"], + connection: { + wsUrl: "ws://127.0.0.1:5226", + mode: "external", + }, + }, + }, + }, + }); + expect(result.changed).toContain( + `config: channels.${LEGACY_CHANNEL_ID} -> channels.${CHANNEL_ID}` + ); + expect(result.changed).toContain( + `config: removed legacy runtime field(s) from channels.${CHANNEL_ID}: cliPath, managed, wsUrl` + ); + expect(result.changed).toContain( + `config: removed legacy runtime field(s) from channels.${CHANNEL_ID}.connection: authToken, dbFilePrefix` + ); + expect(result.changed).toContain( + `config: removed legacy runtime field(s) from channels.${CHANNEL_ID}.accounts.ops.connection: displayName` + ); + }); + + it("normalizes already-renamed configs when migrate is run after a partial manual upgrade", () => { + const { nextConfig, result } = migrateConfigObject({ + channels: { + [CHANNEL_ID]: { + connection: { + wsUrl: "ws://localhost:5225", + fullName: "SimpleX Agent", + }, + accounts: { + support: { + managed: false, + connection: { + token: "old", + autoAcceptFiles: false, + }, + }, + }, + }, + }, + }); + + expect(nextConfig.channels).toEqual({ + [CHANNEL_ID]: { + connection: { + wsUrl: "ws://localhost:5225", + mode: "external", + }, + accounts: { + support: { + connection: { + autoAcceptFiles: false, + mode: "external", + }, + }, + }, + }, + }); + expect(result.changed).toContain( + `config: removed legacy runtime field(s) from channels.${CHANNEL_ID}.connection: fullName` + ); + expect(result.changed).toContain( + `config: removed legacy runtime field(s) from channels.${CHANNEL_ID}.accounts.support: managed` + ); + expect(result.changed).toContain( + `config: removed legacy runtime field(s) from channels.${CHANNEL_ID}.accounts.support.connection: token` + ); + }); }); describe("simplex migration state", () => { @@ -154,3 +315,109 @@ describe("simplex migration state", () => { expect(files).toEqual([`${LEGACY_CHANNEL_ID}-pairing.json`]); }); }); + +describe("simplex migration command", () => { + it("uses the runtime config snapshot and explicit replacement writer", async () => { + const stateDir = path.join(os.tmpdir(), `openclaw-simplex-test-${Date.now()}-runtime`); + const writes: unknown[] = []; + const api = { + runtime: { + config: { + current: () => ({ + channels: { + [CHANNEL_ID]: { + connection: { + wsUrl: "ws://127.0.0.1:5225", + dbFilePrefix: "~/.openclaw/simplex/kept", + }, + }, + }, + }), + replaceConfigFile: async (params: unknown) => { + writes.push(params); + }, + loadConfig: () => { + throw new Error("deprecated loadConfig() must not be used"); + }, + writeConfigFile: () => { + throw new Error("deprecated writeConfigFile() must not be used"); + }, + }, + state: { + resolveStateDir: () => stateDir, + }, + }, + } as unknown as OpenClawPluginApi; + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + + try { + await runMigration(api, false); + } finally { + log.mockRestore(); + } + + expect(writes).toHaveLength(1); + expect(writes[0]).toEqual({ + nextConfig: { + plugins: {}, + channels: { + [CHANNEL_ID]: { + connection: { + wsUrl: "ws://127.0.0.1:5225", + mode: "external", + }, + }, + }, + }, + afterWrite: { + mode: "restart", + reason: "SimpleX migration updated plugin or channel configuration", + }, + }); + }); +}); + +describe("simplex cli metadata", () => { + it("registers operator commands for runtime, requests, groups, links, and migration", () => { + const commands: string[][] = []; + const api = { + registerCli: (registrar: (ctx: { program: FakeCliCommand }) => void) => { + registrar({ program: new FakeCliCommand([], commands) }); + }, + } as unknown as OpenClawPluginApi; + + registerSimplexCliMetadata(api); + + expect(commands).toEqual( + expect.arrayContaining([ + [PLUGIN_ID], + [PLUGIN_ID, "migrate"], + [PLUGIN_ID, "invite"], + [PLUGIN_ID, "invite", "create"], + [PLUGIN_ID, "invite", "list"], + [PLUGIN_ID, "address"], + [PLUGIN_ID, "address", "show"], + [PLUGIN_ID, "address", "create"], + [PLUGIN_ID, "address", "revoke"], + [PLUGIN_ID, "runtime"], + [PLUGIN_ID, "runtime", "status"], + [PLUGIN_ID, "runtime", "doctor"], + [PLUGIN_ID, "runtime", "service"], + [PLUGIN_ID, "runtime", "service", "install"], + [PLUGIN_ID, "requests"], + [PLUGIN_ID, "requests", "list"], + [PLUGIN_ID, "requests", "accept"], + [PLUGIN_ID, "requests", "reject"], + [PLUGIN_ID, "groups"], + [PLUGIN_ID, "groups", "create"], + [PLUGIN_ID, "groups", "link"], + [PLUGIN_ID, "groups", "link", "create"], + [PLUGIN_ID, "groups", "link", "list"], + [PLUGIN_ID, "groups", "link", "revoke"], + [PLUGIN_ID, "connect"], + [PLUGIN_ID, "connect", "plan"], + [PLUGIN_ID, "connect", "run"], + ]) + ); + }); +}); diff --git a/src/cli/plugin-cli.ts b/src/cli/plugin-cli.ts index 0278b76..acf8356 100644 --- a/src/cli/plugin-cli.ts +++ b/src/cli/plugin-cli.ts @@ -1,42 +1,69 @@ -import { access, mkdir, readdir, rename } from "node:fs/promises"; -import path from "node:path"; import { renderQrTerminal } from "openclaw/plugin-sdk/media-runtime"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; +import { LEGACY_SIMPLEX_PLUGIN_ID, SIMPLEX_PLUGIN_ID } from "../constants.js"; import { - LEGACY_SIMPLEX_CHANNEL_ID, - LEGACY_SIMPLEX_PLUGIN_ID, - SIMPLEX_CHANNEL_ID, - SIMPLEX_PLUGIN_ID, -} from "../constants.js"; + connectSimplexLink, + planSimplexConnectionLink, +} from "../simplex/services/connect-links.js"; +import { + acceptSimplexContactRequest, + listSimplexContactRequests, + rejectSimplexContactRequest, +} from "../simplex/services/contact-requests.js"; +import { + createSimplexGroup, + createSimplexGroupLink, + listSimplexGroupLink, + revokeSimplexGroupLink, +} from "../simplex/services/groups.js"; import { createSimplexInvite, listSimplexInvites, revokeSimplexInvite, -} from "../simplex/simplex-invite-service.js"; +} from "../simplex/services/invites.js"; +import { + doctorSimplexRuntime, + getSimplexRuntimeStatus, +} from "../simplex/services/runtime-status.js"; +import { runMigration } from "./migration.js"; +import { type RuntimeServiceOptions, runRuntimeServiceInstallCli } from "./runtime-service.js"; -export const LEGACY_PLUGIN_ID = LEGACY_SIMPLEX_PLUGIN_ID; -export const PLUGIN_ID = SIMPLEX_PLUGIN_ID; -export const LEGACY_CHANNEL_ID = LEGACY_SIMPLEX_CHANNEL_ID; -export const CHANNEL_ID = SIMPLEX_CHANNEL_ID; +export { migrateConfigObject, migrateStateFiles } from "./migration.js"; -type MigrationResult = { - changed: string[]; - skipped: string[]; -}; +const LEGACY_PLUGIN_ID = LEGACY_SIMPLEX_PLUGIN_ID; +const PLUGIN_ID = SIMPLEX_PLUGIN_ID; -type SimplexMigrationStateApi = { - runtime: { - state: { - resolveStateDir: () => string; - }; - }; +type InviteCliOptions = { + accountId?: string; + qr?: boolean; }; -type InviteCliOptions = { +type AccountCliOptions = { accountId?: string; +}; + +type RuntimeServiceCliOptions = RuntimeServiceOptions; + +type RequestCliOptions = AccountCliOptions & { + contactRequestId?: string; +}; + +type GroupCreateCliOptions = AccountCliOptions & { + displayName?: string; + fullName?: string; + description?: string; +}; + +type GroupLinkCliOptions = AccountCliOptions & { + groupId?: string; + role?: string; qr?: boolean; }; +type ConnectCliOptions = AccountCliOptions & { + link?: string; +}; + function readOptionalAccountId(value?: string): string | null { const trimmed = value?.trim(); return trimmed ? trimmed : null; @@ -51,6 +78,23 @@ function printJson(value: unknown): void { console.log(JSON.stringify(value, null, 2)); } +function readPositiveInteger(value: string | undefined, label: string): number { + const token = value?.trim() ?? ""; + const parsed = /^[0-9]+$/.test(token) ? Number(token) : Number.NaN; + if (!Number.isInteger(parsed) || parsed <= 0) { + throw new Error(`${label} must be a positive integer`); + } + return parsed; +} + +function readRequiredString(value: string | undefined, label: string): string { + const trimmed = value?.trim(); + if (!trimmed) { + throw new Error(`${label} is required`); + } + return trimmed; +} + async function runInviteCreateCli( api: OpenClawPluginApi, mode: "connect" | "address", @@ -65,7 +109,7 @@ async function runInviteCreateCli( printJson({ accountId: result.accountId, mode: result.mode, - command: result.command, + operation: result.operation, link: result.link, }); if (opts.qr && result.link) { @@ -114,191 +158,140 @@ async function runAddressRevokeCli(api: OpenClawPluginApi, opts: InviteCliOption }); printJson({ accountId: result.accountId, - revoked: true, + revoked: result.revoked, }); } -function dedupeStrings(values: unknown[] | undefined): string[] | undefined { - if (!Array.isArray(values)) { - return values as string[] | undefined; - } - return [ - ...new Set( - values.filter( - (value): value is string => typeof value === "string" && value.trim().length > 0 - ) - ), - ]; +async function runRuntimeStatusCli(api: OpenClawPluginApi, opts: AccountCliOptions): Promise { + printJson( + await getSimplexRuntimeStatus({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + }) + ); } -function mergeObjects>( - legacy: T | undefined, - next: T | undefined -): T | undefined { - if (!legacy && !next) { - return undefined; - } - if (!legacy) { - return next; - } - if (!next) { - return legacy; - } - const merged: Record = { ...legacy, ...next }; - if ("connection" in legacy || "connection" in next) { - merged.connection = { - ...((legacy.connection as Record | undefined) ?? {}), - ...((next.connection as Record | undefined) ?? {}), - }; - } - if ("accounts" in legacy || "accounts" in next) { - merged.accounts = { - ...((legacy.accounts as Record | undefined) ?? {}), - ...((next.accounts as Record | undefined) ?? {}), - }; - } - return merged as T; +async function runRuntimeDoctorCli(api: OpenClawPluginApi, opts: AccountCliOptions): Promise { + printJson( + await doctorSimplexRuntime({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + }) + ); } -export function migrateConfigObject(rawConfig: Record): { - nextConfig: Record; - result: MigrationResult; -} { - const result: MigrationResult = { changed: [], skipped: [] }; - const nextConfig: Record = { - ...rawConfig, - plugins: { ...((rawConfig.plugins as Record | undefined) ?? {}) }, - channels: { ...((rawConfig.channels as Record | undefined) ?? {}) }, - }; - - const plugins = nextConfig.plugins as Record; - const channels = nextConfig.channels as Record; - - const entries = { ...((plugins.entries as Record | undefined) ?? {}) }; - if (LEGACY_PLUGIN_ID in entries) { - entries[PLUGIN_ID] = mergeObjects( - entries[LEGACY_PLUGIN_ID] as Record | undefined, - entries[PLUGIN_ID] as Record | undefined - ); - delete entries[LEGACY_PLUGIN_ID]; - plugins.entries = entries; - result.changed.push( - `config: plugins.entries.${LEGACY_PLUGIN_ID} -> plugins.entries.${PLUGIN_ID}` - ); - } +async function runRequestsListCli(api: OpenClawPluginApi, opts: AccountCliOptions): Promise { + printJson( + await listSimplexContactRequests({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + }) + ); +} - const installs = { ...((plugins.installs as Record | undefined) ?? {}) }; - if (LEGACY_PLUGIN_ID in installs) { - installs[PLUGIN_ID] = mergeObjects( - installs[LEGACY_PLUGIN_ID] as Record | undefined, - installs[PLUGIN_ID] as Record | undefined - ); - delete installs[LEGACY_PLUGIN_ID]; - plugins.installs = installs; - result.changed.push( - `config: plugins.installs.${LEGACY_PLUGIN_ID} -> plugins.installs.${PLUGIN_ID}` - ); - } +async function runRequestsAcceptCli( + api: OpenClawPluginApi, + opts: RequestCliOptions +): Promise { + printJson( + await acceptSimplexContactRequest({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + contactRequestId: readPositiveInteger(opts.contactRequestId, "contactRequestId"), + }) + ); +} - for (const key of ["allow", "deny"] as const) { - const values = dedupeStrings(plugins[key] as unknown[] | undefined); - if (!values?.includes(LEGACY_PLUGIN_ID)) { - continue; - } - const migrated = dedupeStrings( - values.map((value) => (value === LEGACY_PLUGIN_ID ? PLUGIN_ID : value)) - ); - plugins[key] = migrated; - result.changed.push( - `config: plugins.${key} replaced "${LEGACY_PLUGIN_ID}" with "${PLUGIN_ID}"` - ); - } +async function runRequestsRejectCli( + api: OpenClawPluginApi, + opts: RequestCliOptions +): Promise { + printJson( + await rejectSimplexContactRequest({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + contactRequestId: readPositiveInteger(opts.contactRequestId, "contactRequestId"), + }) + ); +} - if (LEGACY_CHANNEL_ID in channels) { - channels[CHANNEL_ID] = mergeObjects( - channels[LEGACY_CHANNEL_ID] as Record | undefined, - channels[CHANNEL_ID] as Record | undefined - ); - delete channels[LEGACY_CHANNEL_ID]; - result.changed.push(`config: channels.${LEGACY_CHANNEL_ID} -> channels.${CHANNEL_ID}`); - } +async function runGroupCreateCli( + api: OpenClawPluginApi, + opts: GroupCreateCliOptions +): Promise { + printJson( + await createSimplexGroup({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + displayName: readRequiredString(opts.displayName, "displayName"), + fullName: opts.fullName, + description: opts.description, + }) + ); +} - return { nextConfig, result }; +async function runGroupLinkCreateCli( + api: OpenClawPluginApi, + opts: GroupLinkCliOptions +): Promise { + const result = await createSimplexGroupLink({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + groupId: readPositiveInteger(opts.groupId, "groupId"), + role: opts.role, + }); + printJson(result); + if (opts.qr && result.link) { + await printTerminalQr(result.link); + } } -export async function migrateStateFiles( - api: SimplexMigrationStateApi, - dryRun: boolean -): Promise { - const result: MigrationResult = { changed: [], skipped: [] }; - const credentialsDir = path.join(api.runtime.state.resolveStateDir(), "credentials"); - await mkdir(credentialsDir, { recursive: true }); - const entries = await readdir(credentialsDir, { withFileTypes: true }); - - for (const entry of entries) { - if (!entry.isFile()) { - continue; - } - const source = entry.name; - let target: string | null = null; - if (source === `${LEGACY_CHANNEL_ID}-pairing.json`) { - target = `${CHANNEL_ID}-pairing.json`; - } else if (source === `${LEGACY_CHANNEL_ID}-allowFrom.json`) { - target = `${CHANNEL_ID}-allowFrom.json`; - } else { - const accountMatch = source.match(/^simplex-(.+)-allowFrom\.json$/); - if (accountMatch?.[1]) { - target = `${CHANNEL_ID}-${accountMatch[1]}-allowFrom.json`; - } - } - if (!target) { - continue; - } - const sourcePath = path.join(credentialsDir, source); - const targetPath = path.join(credentialsDir, target); - try { - await access(targetPath); - result.skipped.push(`state: skipped ${source} because ${target} already exists`); - continue; - } catch {} - if (!dryRun) { - await rename(sourcePath, targetPath); - } - result.changed.push(`state: ${source} -> ${target}`); +async function runGroupLinkListCli( + api: OpenClawPluginApi, + opts: GroupLinkCliOptions +): Promise { + const result = await listSimplexGroupLink({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + groupId: readPositiveInteger(opts.groupId, "groupId"), + }); + printJson(result); + if (opts.qr && result.link) { + await printTerminalQr(result.link); } +} - return result; +async function runGroupLinkRevokeCli( + api: OpenClawPluginApi, + opts: GroupLinkCliOptions +): Promise { + printJson( + await revokeSimplexGroupLink({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + groupId: readPositiveInteger(opts.groupId, "groupId"), + }) + ); } -function printMigrationResult(result: MigrationResult, dryRun: boolean): void { - const title = dryRun - ? "OpenClaw SimpleX migration dry run" - : "OpenClaw SimpleX migration complete"; - console.log(title); - if (result.changed.length === 0) { - console.log("- No changes were needed."); - } else { - for (const line of result.changed) { - console.log(`- ${line}`); - } - } - for (const line of result.skipped) { - console.log(`- ${line}`); - } +async function runConnectPlanCli(api: OpenClawPluginApi, opts: ConnectCliOptions): Promise { + printJson( + await planSimplexConnectionLink({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + link: readRequiredString(opts.link, "link"), + }) + ); } -export async function runMigration(api: OpenClawPluginApi, dryRun: boolean): Promise { - const currentConfig = api.runtime.config.loadConfig() as Record; - const { nextConfig, result: configResult } = migrateConfigObject(currentConfig); - const stateResult = await migrateStateFiles(api, dryRun); - const result: MigrationResult = { - changed: [...configResult.changed, ...stateResult.changed], - skipped: [...configResult.skipped, ...stateResult.skipped], - }; - if (!dryRun && configResult.changed.length > 0) { - await api.runtime.config.writeConfigFile(nextConfig); - } - printMigrationResult(result, dryRun); +async function runConnectCli(api: OpenClawPluginApi, opts: ConnectCliOptions): Promise { + printJson( + await connectSimplexLink({ + cfg: api.config, + accountId: readOptionalAccountId(opts.accountId), + link: readRequiredString(opts.link, "link"), + }) + ); } const SIMPLEX_CLI_COMMANDS = [PLUGIN_ID, LEGACY_PLUGIN_ID]; @@ -326,7 +319,7 @@ export function registerSimplexCliMetadata(api: OpenClawPluginApi): void { command .command("migrate") - .description("Migrate config and OpenClaw state from simplex -> openclaw-simplex ids") + .description("Migrate SimpleX ids and legacy WebSocket runtime config") .option("--dry-run", "Show planned changes without writing files", false) .action(async (opts: { dryRun?: boolean }) => { await runMigration(api, opts.dryRun === true); @@ -379,6 +372,139 @@ export function registerSimplexCliMetadata(api: OpenClawPluginApi): void { .action(async (opts: InviteCliOptions) => { await runAddressRevokeCli(api, opts); }); + + const runtime = command.command("runtime").description("SimpleX runtime diagnostics"); + + runtime + .command("status") + .description("Show SimpleX runtime status for an account") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: AccountCliOptions) => { + await runRuntimeStatusCli(api, opts); + }); + + runtime + .command("doctor") + .description("Run SimpleX runtime diagnostics for an account") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: AccountCliOptions) => { + await runRuntimeDoctorCli(api, opts); + }); + + const runtimeService = runtime + .command("service") + .description("Manage a host service for the external simplex-chat runtime"); + + runtimeService + .command("install") + .description("Write the service file after interactive approval") + .option( + "--provider ", + "Service manager: auto, systemd, launchd, or sysvinit", + "auto" + ) + .option("--port ", "simplex-chat WebSocket port", "5225") + .option("--simplex-chat-path ", "Path to simplex-chat", "simplex-chat") + .option("--device-name ", "SimpleX device name", "OpenClaw SimpleX") + .option("--files-folder ", "SimpleX files folder", "~/.simplex/files") + .option("--temp-folder ", "SimpleX temp folder", "~/.simplex/tmp") + .option("--dry-run", "Print the plan without writing files", false) + .action(async (opts: RuntimeServiceCliOptions) => { + await runRuntimeServiceInstallCli(opts); + }); + + const requests = command.command("requests").description("SimpleX contact request helpers"); + + requests + .command("list") + .description("List pending SimpleX contact requests seen by the runtime") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: AccountCliOptions) => { + await runRequestsListCli(api, opts); + }); + + requests + .command("accept") + .description("Accept a pending SimpleX contact request") + .requiredOption("--contact-request-id ", "SimpleX contact request id") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: RequestCliOptions) => { + await runRequestsAcceptCli(api, opts); + }); + + requests + .command("reject") + .description("Reject a pending SimpleX contact request") + .requiredOption("--contact-request-id ", "SimpleX contact request id") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: RequestCliOptions) => { + await runRequestsRejectCli(api, opts); + }); + + const groups = command.command("groups").description("SimpleX group helpers"); + + groups + .command("create") + .description("Create a SimpleX group") + .requiredOption("--display-name ", "SimpleX group display name") + .option("--full-name ", "SimpleX group full name") + .option("--description ", "SimpleX group description") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: GroupCreateCliOptions) => { + await runGroupCreateCli(api, opts); + }); + + const groupLink = groups.command("link").description("SimpleX group link helpers"); + + groupLink + .command("create") + .description("Create a SimpleX group invite link") + .requiredOption("--group-id ", "SimpleX group id") + .option("--role ", "Accepted member role for the group link", "member") + .option("--account-id ", "Use a specific SimpleX account") + .option("--qr", "Print a terminal QR code for the group link", false) + .action(async (opts: GroupLinkCliOptions) => { + await runGroupLinkCreateCli(api, opts); + }); + + groupLink + .command("list") + .description("Show the current SimpleX group invite link") + .requiredOption("--group-id ", "SimpleX group id") + .option("--account-id ", "Use a specific SimpleX account") + .option("--qr", "Print a terminal QR code for the group link", false) + .action(async (opts: GroupLinkCliOptions) => { + await runGroupLinkListCli(api, opts); + }); + + groupLink + .command("revoke") + .description("Revoke the current SimpleX group invite link") + .requiredOption("--group-id ", "SimpleX group id") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: GroupLinkCliOptions) => { + await runGroupLinkRevokeCli(api, opts); + }); + + const connect = command.command("connect").description("SimpleX link onboarding helpers"); + + connect + .command("plan") + .description("Inspect what connecting to a SimpleX link would do") + .requiredOption("--link ", "SimpleX contact, address, or group link") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: ConnectCliOptions) => { + await runConnectPlanCli(api, opts); + }); + + connect + .command("run") + .description("Connect the active SimpleX user to a contact, address, or group link") + .requiredOption("--link ", "SimpleX contact, address, or group link") + .option("--account-id ", "Use a specific SimpleX account") + .action(async (opts: ConnectCliOptions) => { + await runConnectCli(api, opts); + }); }, { commands: [...SIMPLEX_CLI_COMMANDS], diff --git a/src/cli/runtime-service.ts b/src/cli/runtime-service.ts new file mode 100644 index 0000000..4fa2652 --- /dev/null +++ b/src/cli/runtime-service.ts @@ -0,0 +1,334 @@ +import { constants as fsConstants } from "node:fs"; +import { access, mkdir, stat, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { stdin as input, stdout as output } from "node:process"; +import { createInterface } from "node:readline/promises"; + +type RuntimeServiceProvider = "auto" | "systemd" | "launchd" | "sysvinit"; + +export type RuntimeServiceOptions = { + provider?: RuntimeServiceProvider; + port?: string; + simplexChatPath?: string; + deviceName?: string; + filesFolder?: string; + tempFolder?: string; + dryRun?: boolean; +}; + +type ResolvedRuntimeService = { + provider: Exclude; + servicePath: string; + installCommand: string[]; + startCommand: string[]; + stopCommand: string[]; + statusCommand: string[]; + content: string; +}; + +function readPort(value: string | undefined): number { + const token = value?.trim() || "5225"; + const parsed = /^[0-9]+$/.test(token) ? Number(token) : Number.NaN; + if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) { + throw new Error("port must be an integer between 1 and 65535"); + } + return parsed; +} + +function shellQuote(value: string): string { + return value.length > 0 && /^[A-Za-z0-9_./:=@%+-]+$/.test(value) + ? value + : `'${value.replaceAll("'", "'\\''")}'`; +} + +function xmlEscape(value: string): string { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +async function pathExists(value: string): Promise { + try { + await access(value, fsConstants.F_OK); + return true; + } catch { + return false; + } +} + +async function commandExists(command: string): Promise { + const paths = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean); + for (const dir of paths) { + const candidate = path.join(dir, command); + try { + const stats = await stat(candidate); + if (stats.isFile()) { + await access(candidate, fsConstants.X_OK); + return true; + } + } catch { + // keep scanning PATH + } + } + return false; +} + +async function detectProvider( + requested: RuntimeServiceProvider | undefined +): Promise> { + if ( + requested !== undefined && + requested !== "auto" && + requested !== "systemd" && + requested !== "launchd" && + requested !== "sysvinit" + ) { + throw new Error('provider must be "auto", "systemd", "launchd", or "sysvinit"'); + } + if (requested === "systemd" || requested === "launchd" || requested === "sysvinit") { + return requested; + } + if (process.platform === "darwin") { + return "launchd"; + } + if (process.platform === "linux" && (await commandExists("systemctl"))) { + return "systemd"; + } + if (process.platform === "linux" && (await pathExists("/etc/init.d"))) { + return "sysvinit"; + } + throw new Error( + "Could not auto-detect a supported service manager. Use --provider systemd, --provider launchd, or --provider sysvinit." + ); +} + +function expandHome(value: string): string { + return value.startsWith("~/") ? path.join(os.homedir(), value.slice(2)) : value; +} + +function resolveRuntimeService( + provider: Exclude, + opts: RuntimeServiceOptions +): ResolvedRuntimeService { + const home = os.homedir(); + const port = readPort(opts.port); + const simplexChatPath = expandHome(opts.simplexChatPath?.trim() || "simplex-chat"); + const deviceName = opts.deviceName?.trim() || "OpenClaw SimpleX"; + const filesFolder = expandHome(opts.filesFolder?.trim() || "~/.simplex/files"); + const tempFolder = expandHome(opts.tempFolder?.trim() || "~/.simplex/tmp"); + + if (provider === "systemd") { + const servicePath = path.join(home, ".config/systemd/user/simplex-chat.service"); + const execStart = [ + simplexChatPath, + "-p", + String(port), + "--device-name", + deviceName, + "--files-folder", + filesFolder, + "--temp-folder", + tempFolder, + ] + .map(shellQuote) + .join(" "); + return { + provider, + servicePath, + installCommand: ["systemctl", "--user", "daemon-reload"], + startCommand: ["systemctl", "--user", "enable", "--now", "simplex-chat.service"], + stopCommand: ["systemctl", "--user", "disable", "--now", "simplex-chat.service"], + statusCommand: ["systemctl", "--user", "status", "simplex-chat.service"], + content: `[Unit] +Description=SimpleX Chat WebSocket Runtime +After=network-online.target + +[Service] +ExecStart=${execStart} +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target +`, + }; + } + + if (provider === "sysvinit") { + const servicePath = "/etc/init.d/simplex-chat"; + const runAsUser = process.env.SUDO_USER?.trim() || os.userInfo().username; + const daemonArgs = [ + simplexChatPath, + "-p", + String(port), + "--device-name", + deviceName, + "--files-folder", + filesFolder, + "--temp-folder", + tempFolder, + ] + .map(shellQuote) + .join(" "); + return { + provider, + servicePath, + installCommand: [ + "sh", + "-c", + `chmod +x ${shellQuote(servicePath)} && (command -v update-rc.d >/dev/null && update-rc.d simplex-chat defaults || true)`, + ], + startCommand: ["service", "simplex-chat", "start"], + stopCommand: ["service", "simplex-chat", "stop"], + statusCommand: ["service", "simplex-chat", "status"], + content: `#!/bin/sh +### BEGIN INIT INFO +# Provides: simplex-chat +# Required-Start: $network +# Required-Stop: $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: SimpleX Chat WebSocket Runtime +### END INIT INFO + +RUN_AS_USER=${shellQuote(runAsUser)} +PIDFILE=/var/run/simplex-chat-openclaw.pid + +case "$1" in + start) + start-stop-daemon --start --background --make-pidfile --pidfile "$PIDFILE" --chuid "$RUN_AS_USER" --startas /bin/sh -- -c 'exec "$@"' simplex-chat-openclaw ${daemonArgs} + ;; + stop) + start-stop-daemon --stop --pidfile "$PIDFILE" --retry TERM/10/KILL/5 + rm -f "$PIDFILE" + ;; + status) + start-stop-daemon --status --pidfile "$PIDFILE" + ;; + restart) + "$0" stop + "$0" start + ;; + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 2 + ;; +esac +exit $? +`, + }; + } + + const servicePath = path.join(home, "Library/LaunchAgents/ai.openclaw.simplex-chat.plist"); + const args = [ + simplexChatPath, + "-p", + String(port), + "--device-name", + deviceName, + "--files-folder", + filesFolder, + "--temp-folder", + tempFolder, + ]; + const argXml = args.map((arg) => ` ${xmlEscape(arg)}`).join("\n"); + return { + provider, + servicePath, + installCommand: [ + "launchctl", + "bootstrap", + `gui/${process.getuid?.() ?? "$(id -u)"}`, + servicePath, + ], + startCommand: [ + "launchctl", + "kickstart", + "-k", + `gui/${process.getuid?.() ?? "$(id -u)"}/ai.openclaw.simplex-chat`, + ], + stopCommand: ["launchctl", "bootout", `gui/${process.getuid?.() ?? "$(id -u)"}`, servicePath], + statusCommand: [ + "launchctl", + "print", + `gui/${process.getuid?.() ?? "$(id -u)"}/ai.openclaw.simplex-chat`, + ], + content: ` + + + + Label + ai.openclaw.simplex-chat + ProgramArguments + +${argXml} + + RunAtLoad + + KeepAlive + + + +`, + }; +} + +function printServicePlan(service: ResolvedRuntimeService): void { + console.log( + JSON.stringify( + { + provider: service.provider, + servicePath: service.servicePath, + installCommand: service.installCommand, + startCommand: service.startCommand, + stopCommand: service.stopCommand, + statusCommand: service.statusCommand, + content: service.content, + }, + null, + 2 + ) + ); +} + +function printCommand(label: string, command: string[]): void { + console.log(`${label}: ${command.map(shellQuote).join(" ")}`); +} + +async function confirmAction(message: string): Promise { + if (!input.isTTY) { + throw new Error(`${message} Refusing because stdin is not interactive.`); + } + const rl = createInterface({ input, output }); + try { + const answer = (await rl.question(`${message} Type "y" to continue: `)).trim().toLowerCase(); + return answer === "y" || answer === "yes"; + } finally { + rl.close(); + } +} + +export async function runRuntimeServiceInstallCli(opts: RuntimeServiceOptions): Promise { + const provider = await detectProvider(opts.provider); + const service = resolveRuntimeService(provider, opts); + printServicePlan(service); + if (opts.dryRun) { + return; + } + const approved = await confirmAction( + `Create directory ${path.dirname(service.servicePath)} and write ${service.servicePath}.` + ); + if (!approved) { + console.log("Skipped."); + return; + } + await mkdir(path.dirname(service.servicePath), { recursive: true }); + await writeFile(service.servicePath, service.content, "utf8"); + console.log(`Wrote ${service.servicePath}`); + printCommand("Run after install", service.installCommand); + printCommand("Start service", service.startCommand); +} diff --git a/src/config/accounts.test.ts b/src/config/accounts.test.ts index 96a916e..c2d53d7 100644 --- a/src/config/accounts.test.ts +++ b/src/config/accounts.test.ts @@ -56,19 +56,21 @@ describe("simplex accounts", () => { expect(resolveDefaultSimplexAccountId(cfg)).toBe("beta"); }); - it("merges connection config across base and account", () => { + it("merges WebSocket runtime config across base and account", () => { const cfg = { channels: { "openclaw-simplex": { enabled: true, connection: { - wsHost: "base-host", - wsPort: 4111, + wsHost: "127.0.0.1", + wsPort: 5225, + connectTimeoutMs: 4111, }, accounts: { alpha: { connection: { - wsPort: 5225, + wsPort: 6225, + connectTimeoutMs: 5225, }, }, }, @@ -78,9 +80,8 @@ describe("simplex accounts", () => { const account = resolveSimplexAccount({ cfg, accountId: "alpha" }); expect(account.mode).toBe("external"); - expect(account.wsHost).toBe("base-host"); - expect(account.wsPort).toBe(5225); - expect(account.wsUrl).toBe("ws://base-host:5225"); + expect(account.wsUrl).toBe("ws://127.0.0.1:6225"); + expect(account.config.connection?.connectTimeoutMs).toBe(5225); expect(account.enabled).toBe(true); }); @@ -112,34 +113,35 @@ describe("simplex accounts", () => { expect(resolveSimplexAccount({ cfg: cfg2, accountId: "alpha" }).enabled).toBe(false); }); - it("uses explicit wsUrl for external mode configuration", () => { + it("uses explicit wsUrl for external runtime", () => { const cfg = { channels: { "openclaw-simplex": { - accounts: { - alpha: { - connection: { - wsHost: "127.0.0.1", - wsPort: 5225, - }, - }, - beta: { - connection: { - mode: "external", - wsUrl: "ws://example.test:9999", - }, - }, + connection: { + wsUrl: "ws://127.0.0.1:5225", }, }, }, } as OpenClawConfig; - const alpha = resolveSimplexAccount({ cfg, accountId: "alpha" }); - expect(alpha.wsUrl).toBe("ws://127.0.0.1:5225"); - expect(alpha.configured).toBe(true); + const account = resolveSimplexAccount({ cfg, accountId: "default" }); + expect(account.mode).toBe("external"); + expect(account.configured).toBe(true); + expect(account.wsUrl).toBe("ws://127.0.0.1:5225"); + }); - const beta = resolveSimplexAccount({ cfg, accountId: "beta" }); - expect(beta.configured).toBe(true); + it("does not treat an empty channel section as configured", () => { + const cfg = { + channels: { + "openclaw-simplex": { + enabled: true, + }, + }, + } as OpenClawConfig; + + const account = resolveSimplexAccount({ cfg, accountId: "default" }); + expect(account.configured).toBe(false); + expect(account.wsUrl).toBe("ws://127.0.0.1:5225"); }); it("treats missing channel config as unconfigured", () => { @@ -149,19 +151,20 @@ describe("simplex accounts", () => { expect(resolveSimplexAccount({ cfg, accountId: "default" }).configured).toBe(false); }); - it("treats explicit ws connection config as configured", () => { + it("supports nested connection config", () => { const cfg = { channels: { "openclaw-simplex": { connection: { - wsHost: "127.0.0.1", - wsPort: 5225, + wsHost: "localhost", + wsPort: 7001, }, }, }, } as OpenClawConfig; - expect(hasMeaningfulSimplexConfig({ cfg })).toBe(true); - expect(resolveSimplexAccount({ cfg, accountId: "default" }).configured).toBe(true); + const account = resolveSimplexAccount({ cfg, accountId: "default" }); + expect(account.configured).toBe(true); + expect(account.wsUrl).toBe("ws://localhost:7001"); }); }); diff --git a/src/config/accounts.ts b/src/config/accounts.ts index cc94bb9..1a937d4 100644 --- a/src/config/accounts.ts +++ b/src/config/accounts.ts @@ -1,12 +1,8 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id"; import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; import { SIMPLEX_CHANNEL_ID } from "../constants.js"; +import type { ResolvedSimplexAccount, SimplexConnectionConfig } from "../types/config.js"; import type { SimplexAccountConfig, SimplexChannelConfig } from "./config-schema.js"; -import type { - ResolvedSimplexAccount, - SimplexConnectionConfig, - SimplexConnectionMode, -} from "./types.js"; const DEFAULT_WS_HOST = "127.0.0.1"; const DEFAULT_WS_PORT = 5225; @@ -114,17 +110,15 @@ export function resolveSimplexAccount(params: { const baseEnabled = params.cfg.channels?.[SIMPLEX_CHANNEL_ID]?.enabled !== false; const enabled = baseEnabled && merged.enabled !== false; const connection = merged.connection ?? {}; - const mode: SimplexConnectionMode = "external"; const wsUrl = resolveWsUrl(connection); const wsHost = resolveWsHost(connection); const wsPort = resolveWsPort(connection); - const configured = hasMeaningfulConfig; return { accountId, enabled, name: merged.name?.trim() || undefined, - configured, - mode, + configured: hasMeaningfulConfig, + mode: "external", wsUrl, wsHost, wsPort, diff --git a/src/config/config-schema.test.ts b/src/config/config-schema.test.ts index 25b73e1..50c893e 100644 --- a/src/config/config-schema.test.ts +++ b/src/config/config-schema.test.ts @@ -6,6 +6,11 @@ import { simplexChannelConfigUiHints } from "./config-ui-hints.js"; const manifest = JSON.parse( readFileSync(new URL("../../openclaw.plugin.json", import.meta.url), "utf8") ) as { + activation?: { + onStartup?: unknown; + onChannels?: unknown; + onCapabilities?: unknown; + }; commandAliases?: unknown; contracts?: { tools?: unknown; @@ -26,6 +31,14 @@ const packageJson = JSON.parse( ) as { openclaw?: { setupEntry?: string; + compat?: { + pluginApi?: string; + minGatewayVersion?: string; + }; + build?: { + openclawVersion?: string; + pluginSdkVersion?: string; + }; channel?: { id?: string; label?: string; @@ -35,6 +48,7 @@ const packageJson = JSON.parse( systemImage?: string; selectionExtras?: string[]; markdownCapable?: boolean; + forceAccountBinding?: boolean; exposure?: { configured?: boolean; setup?: boolean; @@ -66,14 +80,23 @@ describe("simplex config schema manifest", () => { expect(channelManifest?.description).toBe(packageJson.openclaw?.channel?.blurb); }); - it("advertises the 2026.4.23 channel selection metadata", () => { - expect(packageJson.openclaw?.install?.minHostVersion).toBe(">=2026.4.23"); + it("advertises the 2026.5.4 compatibility and channel selection metadata", () => { + expect(packageJson.openclaw?.install?.minHostVersion).toBe(">=2026.5.4"); + expect(packageJson.openclaw?.compat).toEqual({ + pluginApi: ">=2026.5.4", + minGatewayVersion: "2026.5.4", + }); + expect(packageJson.openclaw?.build).toEqual({ + openclawVersion: "2026.5.4", + pluginSdkVersion: "2026.5.4", + }); expect(packageJson.openclaw?.channel).toMatchObject({ detailLabel: "SimpleX Chat", aliases: ["simplex"], systemImage: "link.badge.plus", selectionExtras: ["Invite-based reachability", "External WebSocket runtime"], markdownCapable: true, + forceAccountBinding: true, exposure: { configured: true, setup: true, @@ -106,6 +129,14 @@ describe("simplex config schema manifest", () => { expect(manifest.commandAliases).toEqual(expect.arrayContaining(["simplex"])); }); + it("opts out of deprecated implicit startup loading", () => { + expect(manifest.activation).toMatchObject({ + onStartup: false, + onChannels: ["openclaw-simplex"], + onCapabilities: ["channel", "tool"], + }); + }); + it("does not advertise a setup entry that would suppress plugin CLI loading", () => { expect(packageJson.openclaw?.setupEntry).toBeUndefined(); }); diff --git a/src/config/config-schema.ts b/src/config/config-schema.ts index 35127c2..24504d5 100644 --- a/src/config/config-schema.ts +++ b/src/config/config-schema.ts @@ -3,6 +3,7 @@ import { BlockStreamingCoalesceSchema, buildCatchallMultiAccountChannelSchema, buildChannelConfigSchema, + ContextVisibilityModeSchema, DmConfigSchema, DmPolicySchema, GroupPolicySchema, @@ -34,6 +35,7 @@ const SimplexConnectionSchema = z wsUrl: z.string().url().optional(), wsHost: z.string().optional(), wsPort: z.number().int().positive().optional(), + allowUnsafeRemoteWs: z.boolean().optional(), autoAcceptFiles: z.boolean().optional(), connectTimeoutMs: z.number().int().positive().optional(), }) @@ -49,6 +51,7 @@ export const SimplexAccountConfigSchema = z reactionLevel: SimplexReactionLevelSchema, dmPolicy: DmPolicySchema.optional(), dmHistoryLimit: z.number().int().min(0).optional(), + contextVisibility: ContextVisibilityModeSchema.optional(), dms: z.record(z.string(), DmConfigSchema.optional()).optional(), allowFrom: SimplexAllowFromListSchema, blockStreaming: z.boolean().optional(), diff --git a/src/config/config-ui-hints.ts b/src/config/config-ui-hints.ts index d43919a..b317ef2 100644 --- a/src/config/config-ui-hints.ts +++ b/src/config/config-ui-hints.ts @@ -20,6 +20,47 @@ const accountScopedUiHints = withAccountScope({ label: "Enabled", help: "Disable an account without deleting its SimpleX configuration.", }, + connection: { + label: "WebSocket Runtime", + help: "Connection settings for the external simplex-chat WebSocket runtime.", + tags: ["transport"], + }, + "connection.wsUrl": { + label: "WebSocket URL", + help: "WebSocket URL for the external simplex-chat runtime.", + placeholder: "ws://127.0.0.1:5225", + tags: ["transport"], + }, + "connection.wsHost": { + label: "WebSocket Host", + help: "Host used to build the runtime WebSocket URL when wsUrl is omitted.", + placeholder: "127.0.0.1", + advanced: true, + }, + "connection.wsPort": { + label: "WebSocket Port", + help: "Port used to build the runtime WebSocket URL when wsUrl is omitted.", + placeholder: "5225", + advanced: true, + }, + "connection.allowUnsafeRemoteWs": { + label: "Allow Unsafe Remote WS", + help: "Allow plaintext ws:// connections to non-loopback hosts only when protected by a private network, firewall, or TLS proxy.", + advanced: true, + tags: ["transport", "security"], + }, + "connection.autoAcceptFiles": { + label: "Auto Accept Files", + help: "Automatically accept incoming file transfers from the SimpleX runtime.", + advanced: true, + tags: ["media"], + }, + "connection.connectTimeoutMs": { + label: "Connect Timeout (ms)", + help: "Runtime connection/start timeout in milliseconds.", + advanced: true, + tags: ["transport"], + }, mediaMaxMb: { label: "Max Media Size (MB)", help: "Optional media size cap for files sent through this SimpleX account.", @@ -59,6 +100,12 @@ const accountScopedUiHints = withAccountScope({ help: "Optional direct-message transcript window to retain for reply context.", advanced: true, }, + contextVisibility: { + label: "Context Visibility", + help: "Controls whether supplemental chat context is visible broadly or only for allowlisted senders.", + advanced: true, + tags: ["security"], + }, allowFrom: { label: "DM Allowlist", help: 'Allowed SimpleX senders for direct messages. Use ["*"] only when you intentionally want broad DM reachability.', @@ -114,53 +161,12 @@ const accountScopedUiHints = withAccountScope({ help: "Optional per-group tool policy override for this SimpleX group.", advanced: true, }, - connection: { - label: "Connection", - help: "Transport settings for the external simplex-chat WebSocket runtime.", - }, - "connection.mode": { - label: "Connection Mode", - help: "SimpleX currently supports only the external runtime mode in this plugin.", - advanced: true, - }, - "connection.wsUrl": { - label: "WebSocket URL", - help: "WebSocket URL for the running simplex-chat API, for example ws://127.0.0.1:5225.", - placeholder: "ws://127.0.0.1:5225", - tags: ["transport"], - }, - "connection.wsHost": { - label: "WebSocket Host", - help: "Host override used when wsUrl is not set explicitly.", - placeholder: "127.0.0.1", - advanced: true, - tags: ["transport"], - }, - "connection.wsPort": { - label: "WebSocket Port", - help: "Port override used when wsUrl is not set explicitly.", - placeholder: "5225", - advanced: true, - tags: ["transport"], - }, - "connection.autoAcceptFiles": { - label: "Auto Accept Files", - help: "Automatically accept incoming file transfers from the SimpleX runtime.", - advanced: true, - tags: ["media"], - }, - "connection.connectTimeoutMs": { - label: "Connect Timeout (ms)", - help: "WebSocket connection timeout in milliseconds.", - advanced: true, - tags: ["transport"], - }, }); export const simplexChannelConfigUiHints = { "": { label: "SimpleX", - help: "Connect OpenClaw to a separately running simplex-chat WebSocket runtime. Reachability starts from SimpleX invite or address links, while OpenClaw still applies pairing, allowlists, and group policy.", + help: "Connect OpenClaw to SimpleX through an external simplex-chat WebSocket runtime. Reachability starts from SimpleX invite or address links, while OpenClaw still applies pairing, allowlists, and group policy.", }, defaultAccount: { label: "Default Account", diff --git a/src/constants.ts b/src/constants.ts index f3c1bb7..bdce6d9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,8 +15,3 @@ export function stripSimplexProviderPrefix(value: string): string { } return trimmed; } - -export function hasSimplexProviderPrefix(value: string): boolean { - const lower = value.trim().toLowerCase(); - return SIMPLEX_PROVIDER_PREFIXES.some((prefix) => lower.startsWith(`${prefix}:`)); -} diff --git a/src/gateway/methods.ts b/src/gateway/methods.ts index 855f853..bc66d2e 100644 --- a/src/gateway/methods.ts +++ b/src/gateway/methods.ts @@ -1,26 +1,34 @@ +import { renderQrPngDataUrl } from "openclaw/plugin-sdk/media-runtime"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; -import { toDataURL as toQrDataUrl } from "qrcode"; -import { resolveDefaultSimplexAccountId } from "../config/accounts.js"; -import { SIMPLEX_CHANNEL_ID } from "../constants.js"; -import { resolveInviteMode } from "../simplex/simplex-invite.js"; +import { + connectSimplexLink, + planSimplexConnectionLink, +} from "../simplex/services/connect-links.js"; +import { + acceptSimplexContactRequest, + listSimplexContactRequests, + rejectSimplexContactRequest, +} from "../simplex/services/contact-requests.js"; +import { + createSimplexGroup, + createSimplexGroupLink, + listSimplexGroupLink, + revokeSimplexGroupLink, +} from "../simplex/services/groups.js"; import { createSimplexInvite, listSimplexInvites, + resolveInviteMode, revokeSimplexInvite, -} from "../simplex/simplex-invite-service.js"; +} from "../simplex/services/invites.js"; +import { + doctorSimplexRuntime, + getSimplexRuntimeStatus, +} from "../simplex/services/runtime-status.js"; const INVALID_REQUEST = "INVALID_REQUEST"; const UNAVAILABLE = "UNAVAILABLE"; -type RuntimeChannelState = { - running?: boolean; -}; - -type RuntimeSnapshot = { - channelAccounts?: Record | undefined>; - channels?: Record; -}; - type GatewayError = { code: string; message: string; @@ -31,7 +39,7 @@ function createError(code: string, message: string): GatewayError { } async function renderQrDataUrl(value: string): Promise { - return await toQrDataUrl(value, { errorCorrectionLevel: "M", margin: 1, scale: 8 }); + return await renderQrPngDataUrl(value, { marginModules: 1, scale: 8 }); } function readAccountId(params: Record | undefined): string | null { @@ -39,21 +47,36 @@ function readAccountId(params: Record | undefined): string | nu return rawAccountId || null; } -function createRuntimeChecker( - context: { getRuntimeSnapshot: () => RuntimeSnapshot }, - accountId: string -): () => boolean { - return () => { - const runtime = context.getRuntimeSnapshot(); - const accountRuntime = runtime.channelAccounts?.[SIMPLEX_CHANNEL_ID]?.[accountId]; - return Boolean(accountRuntime?.running ?? runtime.channels?.[SIMPLEX_CHANNEL_ID]?.running); - }; +function readRequiredString(params: Record | undefined, key: string): string { + const value = typeof params?.[key] === "string" ? params[key].trim() : ""; + if (!value) { + throw new Error(`${key} is required`); + } + return value; +} + +function readRequiredInteger(params: Record | undefined, key: string): number { + const raw = params?.[key]; + const value = + typeof raw === "number" + ? raw + : typeof raw === "string" && /^[0-9]+$/.test(raw.trim()) + ? Number(raw.trim()) + : NaN; + if (!Number.isInteger(value) || value <= 0) { + throw new Error(`${key} must be a positive integer`); + } + return value; +} + +function unavailable(prefix: string, err: unknown): GatewayError { + return createError(UNAVAILABLE, `${prefix}: ${err instanceof Error ? err.message : String(err)}`); } export function registerSimplexGatewayMethods(api: OpenClawPluginApi): void { api.registerGatewayMethod( "simplex.invite.create", - async ({ params, respond, context }) => { + async ({ params, respond }) => { const mode = resolveInviteMode(params?.mode); if (!mode) { respond( @@ -71,23 +94,11 @@ export function registerSimplexGatewayMethods(api: OpenClawPluginApi): void { accountId, mode, logger: api.logger, - startChannel: () => context.startChannel(SIMPLEX_CHANNEL_ID, accountId ?? undefined), - isRunning: createRuntimeChecker( - context, - accountId ?? resolveDefaultSimplexAccountId(api.config) - ), }); const qrDataUrl = result.link ? await renderQrDataUrl(result.link) : null; respond(true, { ...result, qrDataUrl }); } catch (err) { - respond( - false, - undefined, - createError( - UNAVAILABLE, - `SimpleX invite failed: ${err instanceof Error ? err.message : String(err)}` - ) - ); + respond(false, undefined, unavailable("SimpleX invite failed", err)); } }, { scope: "operator.write" } @@ -95,18 +106,13 @@ export function registerSimplexGatewayMethods(api: OpenClawPluginApi): void { api.registerGatewayMethod( "simplex.invite.list", - async ({ params, respond, context }) => { + async ({ params, respond }) => { const accountId = readAccountId(params); try { const result = await listSimplexInvites({ cfg: api.config, accountId, logger: api.logger, - startChannel: () => context.startChannel(SIMPLEX_CHANNEL_ID, accountId ?? undefined), - isRunning: createRuntimeChecker( - context, - accountId ?? resolveDefaultSimplexAccountId(api.config) - ), }); const addressQrDataUrl = result.addressLink ? await renderQrDataUrl(result.addressLink) @@ -116,14 +122,7 @@ export function registerSimplexGatewayMethods(api: OpenClawPluginApi): void { addressQrDataUrl, }); } catch (err) { - respond( - false, - undefined, - createError( - UNAVAILABLE, - `SimpleX invite list failed: ${err instanceof Error ? err.message : String(err)}` - ) - ); + respond(false, undefined, unavailable("SimpleX invite list failed", err)); } }, { scope: "operator.read" } @@ -131,29 +130,219 @@ export function registerSimplexGatewayMethods(api: OpenClawPluginApi): void { api.registerGatewayMethod( "simplex.invite.revoke", - async ({ params, respond, context }) => { + async ({ params, respond }) => { const accountId = readAccountId(params); try { const result = await revokeSimplexInvite({ cfg: api.config, accountId, logger: api.logger, - startChannel: () => context.startChannel(SIMPLEX_CHANNEL_ID, accountId ?? undefined), - isRunning: createRuntimeChecker( - context, - accountId ?? resolveDefaultSimplexAccountId(api.config) - ), }); respond(true, result); } catch (err) { + respond(false, undefined, unavailable("SimpleX invite revoke failed", err)); + } + }, + { scope: "operator.admin" } + ); + + api.registerGatewayMethod( + "simplex.runtime.status", + async ({ params, respond }) => { + try { respond( - false, - undefined, - createError( - UNAVAILABLE, - `SimpleX invite revoke failed: ${err instanceof Error ? err.message : String(err)}` - ) + true, + await getSimplexRuntimeStatus({ cfg: api.config, accountId: readAccountId(params) }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX runtime status failed", err)); + } + }, + { scope: "operator.read" } + ); + + api.registerGatewayMethod( + "simplex.runtime.doctor", + async ({ params, respond }) => { + try { + respond( + true, + await doctorSimplexRuntime({ cfg: api.config, accountId: readAccountId(params) }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX runtime doctor failed", err)); + } + }, + { scope: "operator.read" } + ); + + api.registerGatewayMethod( + "simplex.requests.list", + async ({ params, respond }) => { + try { + respond( + true, + await listSimplexContactRequests({ cfg: api.config, accountId: readAccountId(params) }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX request list failed", err)); + } + }, + { scope: "operator.read" } + ); + + api.registerGatewayMethod( + "simplex.requests.accept", + async ({ params, respond }) => { + try { + respond( + true, + await acceptSimplexContactRequest({ + cfg: api.config, + accountId: readAccountId(params), + contactRequestId: readRequiredInteger(params, "contactRequestId"), + }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX request accept failed", err)); + } + }, + { scope: "operator.admin" } + ); + + api.registerGatewayMethod( + "simplex.requests.reject", + async ({ params, respond }) => { + try { + respond( + true, + await rejectSimplexContactRequest({ + cfg: api.config, + accountId: readAccountId(params), + contactRequestId: readRequiredInteger(params, "contactRequestId"), + }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX request reject failed", err)); + } + }, + { scope: "operator.admin" } + ); + + api.registerGatewayMethod( + "simplex.groups.create", + async ({ params, respond }) => { + try { + respond( + true, + await createSimplexGroup({ + cfg: api.config, + accountId: readAccountId(params), + displayName: readRequiredString(params, "displayName"), + fullName: typeof params?.fullName === "string" ? params.fullName : undefined, + description: typeof params?.description === "string" ? params.description : undefined, + }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX group create failed", err)); + } + }, + { scope: "operator.admin" } + ); + + api.registerGatewayMethod( + "simplex.groups.link.create", + async ({ params, respond }) => { + try { + const result = await createSimplexGroupLink({ + cfg: api.config, + accountId: readAccountId(params), + groupId: params?.groupId, + role: params?.role, + }); + respond(true, { + ...result, + qrDataUrl: result.link ? await renderQrDataUrl(result.link) : null, + }); + } catch (err) { + respond(false, undefined, unavailable("SimpleX group link create failed", err)); + } + }, + { scope: "operator.admin" } + ); + + api.registerGatewayMethod( + "simplex.groups.link.list", + async ({ params, respond }) => { + try { + const result = await listSimplexGroupLink({ + cfg: api.config, + accountId: readAccountId(params), + groupId: params?.groupId, + }); + respond(true, { + ...result, + qrDataUrl: result.link ? await renderQrDataUrl(result.link) : null, + }); + } catch (err) { + respond(false, undefined, unavailable("SimpleX group link list failed", err)); + } + }, + { scope: "operator.read" } + ); + + api.registerGatewayMethod( + "simplex.groups.link.revoke", + async ({ params, respond }) => { + try { + respond( + true, + await revokeSimplexGroupLink({ + cfg: api.config, + accountId: readAccountId(params), + groupId: params?.groupId, + }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX group link revoke failed", err)); + } + }, + { scope: "operator.admin" } + ); + + api.registerGatewayMethod( + "simplex.connect.plan", + async ({ params, respond }) => { + try { + respond( + true, + await planSimplexConnectionLink({ + cfg: api.config, + accountId: readAccountId(params), + link: readRequiredString(params, "link"), + }) ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX connect plan failed", err)); + } + }, + { scope: "operator.read" } + ); + + api.registerGatewayMethod( + "simplex.connect", + async ({ params, respond }) => { + try { + respond( + true, + await connectSimplexLink({ + cfg: api.config, + accountId: readAccountId(params), + link: readRequiredString(params, "link"), + }) + ); + } catch (err) { + respond(false, undefined, unavailable("SimpleX connect failed", err)); } }, { scope: "operator.admin" } diff --git a/src/simplex/runtime/account.ts b/src/simplex/runtime/account.ts new file mode 100644 index 0000000..a1d44b9 --- /dev/null +++ b/src/simplex/runtime/account.ts @@ -0,0 +1,50 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { resolveDefaultSimplexAccountId, resolveSimplexAccount } from "../../config/accounts.js"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexLogger } from "../../types/simplex.js"; +import type { SimplexClient } from "./client.js"; +import { parseSimplexNumericId } from "./commands.js"; +import { withSimplexClient } from "./transport.js"; + +export function resolveRuntimeAccount( + cfg: OpenClawConfig, + rawAccountId?: string | null +): ResolvedSimplexAccount { + const explicit = rawAccountId?.trim(); + const accountId = explicit || resolveDefaultSimplexAccountId(cfg); + const account = resolveSimplexAccount({ cfg, accountId }); + if (!account.enabled) { + throw new Error(`SimpleX account "${accountId}" is disabled`); + } + if (!account.configured) { + throw new Error(`SimpleX account "${accountId}" is not configured`); + } + return account; +} + +export async function withActiveSimplexUser(params: { + account: ResolvedSimplexAccount; + logger?: SimplexLogger; + run: (userId: number, client: SimplexClient) => Promise; +}): Promise { + return await withSimplexClient({ + account: params.account, + logger: params.logger, + run: async (client) => { + const user = (await client.getActiveUser()) as Record | undefined; + const rawUserId = user?.userId; + const userId = + typeof rawUserId === "number" || typeof rawUserId === "string" + ? parseSimplexNumericId(rawUserId) + : null; + if (userId === null) { + throw new Error(`SimpleX account "${params.account.accountId}" has no active user`); + } + return await params.run(userId, client); + }, + }); +} + +export function readSimplexRuntimeVersion(): string | null { + return null; +} diff --git a/src/simplex/runtime/api.ts b/src/simplex/runtime/api.ts new file mode 100644 index 0000000..86b7e32 --- /dev/null +++ b/src/simplex/runtime/api.ts @@ -0,0 +1,5 @@ +export { + formatSimplexChatRef, + parseSimplexNumericId, + resolveSimplexChatItemId, +} from "./commands.js"; diff --git a/src/simplex/runtime/client.ts b/src/simplex/runtime/client.ts new file mode 100644 index 0000000..67c181b --- /dev/null +++ b/src/simplex/runtime/client.ts @@ -0,0 +1,296 @@ +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { + SimplexComposedMessage, + SimplexDeleteMode, + SimplexGroupMemberRole, + SimplexGroupProfile, + SimplexLogger, + SimplexReaction, + SimplexRuntimeEvent, + SimplexRuntimeResponse, +} from "../../types/simplex.js"; +import { + buildAcceptContactRequestCommand, + buildAddGroupMemberCommand, + buildCancelFileCommand, + buildConnectCommand, + buildConnectPlanCommand, + buildCreateGroupCommand, + buildCreateGroupLinkCommand, + buildDeleteChatItemCommand, + buildDeleteGroupLinkCommand, + buildLeaveGroupCommand, + buildListContactsCommand, + buildListGroupMembersCommand, + buildListGroupsCommand, + buildListUsersCommand, + buildReactionCommand, + buildReceiveFileCommand, + buildRejectContactRequestCommand, + buildRemoveGroupMemberCommand, + buildSendMessagesCommand, + buildShowActiveUserCommand, + buildShowGroupLinkCommand, + buildUpdateChatItemCommand, + buildUpdateGroupProfileCommand, + INVITE_COMMANDS, +} from "./commands.js"; +import { resolveSimplexCommandError } from "./errors.js"; +import { extractSimplexLink } from "./links.js"; +import { assertSimplexWsEndpointAllowed } from "./security.js"; +import { type SimplexConnectionState, SimplexWsClient } from "./ws-client.js"; + +type SimplexClientParams = { + account: ResolvedSimplexAccount; + logger?: SimplexLogger; +}; + +type CommandResponsePayload = { + type?: string; + [key: string]: unknown; +}; + +function unwrapResponse(response: SimplexRuntimeResponse): CommandResponsePayload { + const resp = response.resp as CommandResponsePayload | undefined; + const commandError = resolveSimplexCommandError(resp); + if (commandError) { + throw new Error(commandError); + } + return resp ?? response; +} + +function firstArrayField(payload: CommandResponsePayload, fields: string[]): unknown[] { + for (const field of fields) { + const value = payload[field]; + if (Array.isArray(value)) { + return value; + } + } + return []; +} + +function firstObjectField(payload: CommandResponsePayload, fields: string[]): unknown { + for (const field of fields) { + const value = payload[field]; + if (value !== undefined) { + return value; + } + } + return payload; +} + +export class SimplexClient { + private readonly ws: SimplexWsClient; + + constructor(params: SimplexClientParams) { + assertSimplexWsEndpointAllowed({ + wsUrl: params.account.wsUrl, + allowUnsafeRemoteWs: params.account.config.connection?.allowUnsafeRemoteWs, + }); + this.ws = new SimplexWsClient({ + url: params.account.wsUrl, + connectTimeoutMs: params.account.config.connection?.connectTimeoutMs, + logger: params.logger, + }); + } + + onEvent(handler: (event: SimplexRuntimeEvent) => void): () => void { + return this.ws.onEvent(handler); + } + + onConnectionState(handler: (state: SimplexConnectionState) => void): () => void { + return this.ws.onConnectionState(handler); + } + + getConnectionState(): SimplexConnectionState { + return this.ws.getConnectionState(); + } + + async connect(): Promise { + await this.ws.connect(); + } + + async close(): Promise { + await this.ws.close(); + } + + async runCommand(command: string): Promise { + return unwrapResponse(await this.ws.sendCommand(command)); + } + + async sendMessages(params: { + chatRef: string; + composedMessages: SimplexComposedMessage[]; + }): Promise { + const payload = await this.runCommand(buildSendMessagesCommand(params)); + return firstArrayField(payload, ["chatItems", "items"]); + } + + async reactToMessage(params: { + chatRef: string; + messageId: number; + add: boolean; + reaction: SimplexReaction; + }): Promise { + return await this.runCommand( + buildReactionCommand({ + chatRef: params.chatRef, + chatItemId: params.messageId, + add: params.add, + reaction: params.reaction, + }) + ); + } + + async editMessage(params: { + chatRef: string; + messageId: number; + updatedMessage: SimplexComposedMessage; + }): Promise { + return await this.runCommand( + buildUpdateChatItemCommand({ + chatRef: params.chatRef, + chatItemId: params.messageId, + updatedMessage: params.updatedMessage, + }) + ); + } + + async deleteMessages(params: { + chatRef: string; + messageIds: Array; + deleteMode?: SimplexDeleteMode; + }): Promise { + return await this.runCommand( + buildDeleteChatItemCommand({ + chatRef: params.chatRef, + chatItemIds: params.messageIds, + deleteMode: params.deleteMode, + }) + ); + } + + async receiveFile(fileId: number): Promise { + return await this.runCommand(buildReceiveFileCommand({ fileId })); + } + + async cancelFile(fileId: number): Promise { + return await this.runCommand(buildCancelFileCommand(fileId)); + } + + async createInviteLink(): Promise<{ link: string | null; response: unknown }> { + const response = await this.runCommand(INVITE_COMMANDS.connect); + return { link: extractSimplexLink(response), response }; + } + + async createAddress(): Promise<{ link: string | null; response: unknown }> { + const response = await this.runCommand(INVITE_COMMANDS.address); + return { link: extractSimplexLink(response), response }; + } + + async getAddress(): Promise<{ link: string | null; response: unknown }> { + const response = await this.runCommand("/show_address"); + return { link: extractSimplexLink(response), response }; + } + + async deleteAddress(): Promise { + return await this.runCommand("/delete_address"); + } + + async getActiveUser(): Promise { + const payload = await this.runCommand(buildShowActiveUserCommand()); + return firstObjectField(payload, ["user", "activeUser"]); + } + + async listUsers(): Promise { + const payload = await this.runCommand(buildListUsersCommand()); + return firstArrayField(payload, ["users"]); + } + + async listContacts(userId: number | string): Promise { + const payload = await this.runCommand(buildListContactsCommand(userId)); + return firstArrayField(payload, ["contacts"]); + } + + async listGroups(params: { + userId: number | string; + contactId?: number | string | null; + search?: string | null; + }): Promise { + const payload = await this.runCommand(buildListGroupsCommand(params)); + return firstArrayField(payload, ["groups"]); + } + + async listGroupMembers(params: { + groupId: number | string; + search?: string | null; + }): Promise { + const payload = await this.runCommand(buildListGroupMembersCommand(params)); + return firstArrayField(payload, ["members", "groupMembers"]); + } + + async createGroup(profile: SimplexGroupProfile): Promise { + const payload = await this.runCommand(buildCreateGroupCommand(profile)); + return firstObjectField(payload, ["group", "groupInfo"]); + } + + async updateGroupProfile(params: { + groupId: number | string; + profile: Partial; + }): Promise { + return await this.runCommand(buildUpdateGroupProfileCommand(params)); + } + + async addGroupMember(params: { + groupId: number | string; + contactId: number | string; + }): Promise { + return await this.runCommand(buildAddGroupMemberCommand(params)); + } + + async removeGroupMember(params: { + groupId: number | string; + memberId: number | string; + }): Promise { + return await this.runCommand(buildRemoveGroupMemberCommand(params)); + } + + async leaveGroup(groupId: number | string): Promise { + return await this.runCommand(buildLeaveGroupCommand(groupId)); + } + + async createGroupLink(params: { + groupId: number | string; + role: SimplexGroupMemberRole; + }): Promise<{ link: string | null; response: unknown }> { + const response = await this.runCommand(buildCreateGroupLinkCommand(params)); + return { link: extractSimplexLink(response), response }; + } + + async getGroupLink( + groupId: number | string + ): Promise<{ link: string | null; response: unknown }> { + const response = await this.runCommand(buildShowGroupLinkCommand(groupId)); + return { link: extractSimplexLink(response), response }; + } + + async deleteGroupLink(groupId: number | string): Promise { + return await this.runCommand(buildDeleteGroupLinkCommand(groupId)); + } + + async acceptContactRequest(contactRequestId: number): Promise { + return await this.runCommand(buildAcceptContactRequestCommand(contactRequestId)); + } + + async rejectContactRequest(contactRequestId: number): Promise { + return await this.runCommand(buildRejectContactRequestCommand(contactRequestId)); + } + + async planConnect(link: string): Promise { + return await this.runCommand(buildConnectPlanCommand(link)); + } + + async connectLink(link: string): Promise { + return await this.runCommand(buildConnectCommand(link)); + } +} diff --git a/src/simplex/runtime/commands.test.ts b/src/simplex/runtime/commands.test.ts new file mode 100644 index 0000000..6fb801f --- /dev/null +++ b/src/simplex/runtime/commands.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { parseSimplexNumericId } from "./commands.js"; + +describe("simplex command helpers", () => { + it("parses numeric ids from SimpleX target forms", () => { + expect(parseSimplexNumericId(123)).toBe(123); + expect(parseSimplexNumericId("@123")).toBe(123); + expect(parseSimplexNumericId("#123")).toBe(123); + expect(parseSimplexNumericId("contact:123")).toBe(123); + expect(parseSimplexNumericId("group:123")).toBe(123); + }); + + it("rejects partial numeric ids", () => { + expect(parseSimplexNumericId("123abc")).toBeNull(); + expect(parseSimplexNumericId("contact:123abc")).toBeNull(); + expect(parseSimplexNumericId("@abc")).toBeNull(); + }); +}); diff --git a/src/simplex/simplex-commands.ts b/src/simplex/runtime/commands.ts similarity index 66% rename from src/simplex/simplex-commands.ts rename to src/simplex/runtime/commands.ts index 692d4db..630a085 100644 --- a/src/simplex/simplex-commands.ts +++ b/src/simplex/runtime/commands.ts @@ -1,28 +1,25 @@ -import { hasSimplexProviderPrefix, stripSimplexProviderPrefix } from "../constants.js"; - -export type SimplexChatType = "direct" | "group" | "local"; - -export type SimplexChatRef = { - type: SimplexChatType; - id: number | string; - scope?: string | null; +import { SIMPLEX_PROVIDER_PREFIXES, stripSimplexProviderPrefix } from "../../constants.js"; +import type { + SimplexChatRef, + SimplexComposedMessage, + SimplexDeleteMode, + SimplexGroupProfile, + SimplexReaction, +} from "../../types/simplex.js"; + +export type SimplexInviteMode = "connect" | "address"; + +export const INVITE_COMMANDS: Record = { + connect: "/c", + address: "/ad", }; -export type SimplexMsgContent = { - type: "text" | "link" | "image" | "video" | "voice" | "file" | "report" | "chat" | "unknown"; - text: string; - [key: string]: unknown; -}; - -export type SimplexComposedMessage = { - msgContent: SimplexMsgContent; - quotedItemId?: number; - fileSource?: { - filePath: string; - cryptoArgs?: { fileKey: string; fileNonce: string }; - }; - mentions?: Record; -}; +export function resolveInviteMode(value: unknown): SimplexInviteMode | null { + if (value === "connect" || value === "address") { + return value; + } + return null; +} function isAsciiAlnumUnderscoreOrHyphen(value: string): boolean { for (const ch of value) { @@ -54,6 +51,46 @@ function isSignedIntegerToken(value: string): boolean { return true; } +function normalizeCommandId(value: number | string): string { + if (typeof value === "number" && Number.isFinite(value)) { + return String(Math.trunc(value)); + } + return String(value).trim(); +} + +function normalizeContactRef(value: number | string): string { + const raw = normalizeCommandId(value); + if (!raw) { + return raw; + } + if (raw.startsWith("@")) { + return raw; + } + const lowered = raw.toLowerCase(); + if ( + lowered.startsWith("contact:") || + lowered.startsWith("user:") || + lowered.startsWith("member:") + ) { + return `@${raw.slice(raw.indexOf(":") + 1).trim()}`; + } + return `@${raw}`; +} + +function normalizeGroupRef(value: number | string): string { + const raw = normalizeCommandId(value); + if (!raw) { + return raw; + } + if (raw.startsWith("#")) { + return raw; + } + if (raw.toLowerCase().startsWith("group:")) { + return `#${raw.slice("group:".length).trim()}`; + } + return `#${raw}`; +} + function normalizeSimplexChatRef(raw: string): string { const trimmed = raw.trim(); if (!trimmed) { @@ -87,7 +124,7 @@ function normalizeChatRefToken(value: string): string { const trimmed = value.trim(); const lowered = trimmed.toLowerCase(); const normalized = - hasSimplexProviderPrefix(trimmed) || + SIMPLEX_PROVIDER_PREFIXES.some((prefix) => lowered.startsWith(`${prefix}:`)) || lowered.startsWith("group:") || lowered.startsWith("contact:") || lowered.startsWith("user:") || @@ -120,26 +157,60 @@ function quoteCliArg(value: string): string { return `'${trimmed.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`; } -function hasWhitespace(value: string): boolean { - for (const ch of value) { - if (ch.trim() === "") { - return true; - } +function formatSearchArg(search?: string | null): string { + const trimmed = search?.trim(); + if (!trimmed) { + return ""; } - return false; + if ([...trimmed].some((ch) => ch.trim() === "")) { + return quoteCliArg(trimmed); + } + return trimmed; } -export function formatChatRef(ref: SimplexChatRef): string { +export function formatSimplexChatRef(ref: SimplexChatRef): string { if (ref.type === "local") { - throw new Error("local SimpleX chat refs are not supported for message commands"); + throw new Error("local SimpleX chat refs are not supported"); } if (ref.scope) { - throw new Error("scoped SimpleX chat refs are not supported for message commands"); + throw new Error("scoped SimpleX chat refs are not supported"); } const prefix = ref.type === "direct" ? "@" : "#"; return `${prefix}${ref.id}`; } +export function parseSimplexNumericId(value: number | string): number | null { + if (typeof value === "number") { + return Number.isFinite(value) ? Math.trunc(value) : null; + } + const raw = stripSimplexProviderPrefix(value.trim()); + const normalized = raw.startsWith("@") + ? raw.slice(1) + : raw.startsWith("#") + ? raw.slice(1) + : raw.includes(":") + ? raw.slice(raw.indexOf(":") + 1) + : raw; + const token = normalized.trim(); + if (!isSignedIntegerToken(token)) { + return null; + } + const id = Number.parseInt(token, 10); + return Number.isFinite(id) ? id : null; +} + +export function resolveSimplexChatItemId(chatItem: unknown): string | undefined { + const item = chatItem as { chatItem?: { meta?: { itemId?: unknown } } } | undefined; + const raw = item?.chatItem?.meta?.itemId; + if (typeof raw === "number" && Number.isFinite(raw)) { + return String(raw); + } + if (typeof raw === "string" && raw.trim()) { + return raw.trim(); + } + return undefined; +} + export function buildSendMessagesCommand(params: { chatRef: string; composedMessages: SimplexComposedMessage[]; @@ -162,14 +233,15 @@ export function buildUpdateChatItemCommand(params: { const chatRef = normalizeChatRefToken(params.chatRef); const chatItemId = normalizeChatItemIdToken(params.chatItemId); const liveFlag = params.liveMessage ? " live=on" : ""; - const json = JSON.stringify(params.updatedMessage); - return `/_update item ${chatRef} ${chatItemId}${liveFlag} json ${json}`; + return `/_update item ${chatRef} ${chatItemId}${liveFlag} json ${JSON.stringify( + params.updatedMessage + )}`; } export function buildDeleteChatItemCommand(params: { chatRef: string; chatItemIds: Array; - deleteMode?: "broadcast" | "internal" | "internalMark"; + deleteMode?: SimplexDeleteMode; }): string { const chatRef = normalizeChatRefToken(params.chatRef); const deleteMode = params.deleteMode ?? "broadcast"; @@ -181,93 +253,26 @@ export function buildReactionCommand(params: { chatRef: string; chatItemId: number; add: boolean; - reaction: Record; + reaction: SimplexReaction; }): string { const chatRef = normalizeChatRefToken(params.chatRef); const chatItemId = normalizeChatItemIdToken(params.chatItemId); const toggle = params.add ? "on" : "off"; - const json = JSON.stringify(params.reaction); - return `/_reaction ${chatRef} ${chatItemId} ${toggle} ${json}`; + return `/_reaction ${chatRef} ${chatItemId} ${toggle} ${JSON.stringify(params.reaction)}`; } -export function buildReceiveFileCommand(params: { - fileId: number; - filePath?: string; - inline?: boolean; - encrypt?: boolean; - approvedRelays?: boolean; -}): string { +export function buildReceiveFileCommand(params: { fileId: number }): string { if (!Number.isFinite(params.fileId)) { throw new Error(`invalid SimpleX file id: ${params.fileId}`); } - const flags: string[] = []; - if (params.approvedRelays) { - flags.push("approved_relays=on"); - } - if (typeof params.encrypt === "boolean") { - flags.push(`encrypt=${params.encrypt ? "on" : "off"}`); - } - if (typeof params.inline === "boolean") { - flags.push(`inline=${params.inline ? "on" : "off"}`); - } - const path = params.filePath ? ` ${quoteCliArg(params.filePath)}` : ""; - const suffix = flags.length > 0 ? ` ${flags.join(" ")}` : ""; - return `/freceive ${Math.trunc(params.fileId)}${suffix}${path}`; + return `/freceive ${Math.trunc(params.fileId)}`; } export function buildCancelFileCommand(fileId: number): string { - return `/fcancel ${fileId}`; -} - -function normalizeCommandId(value: number | string): string { - if (typeof value === "number" && Number.isFinite(value)) { - return String(Math.trunc(value)); - } - return String(value).trim(); -} - -function normalizeContactRef(value: number | string): string { - const raw = normalizeCommandId(value); - if (!raw) { - return raw; - } - if (raw.startsWith("@")) { - return raw; - } - const lowered = raw.toLowerCase(); - if ( - lowered.startsWith("contact:") || - lowered.startsWith("user:") || - lowered.startsWith("member:") - ) { - return `@${raw.slice(raw.indexOf(":") + 1).trim()}`; - } - return `@${raw}`; -} - -function normalizeGroupRef(value: number | string): string { - const raw = normalizeCommandId(value); - if (!raw) { - return raw; - } - if (raw.startsWith("#")) { - return raw; - } - if (raw.toLowerCase().startsWith("group:")) { - return `#${raw.slice("group:".length).trim()}`; + if (!Number.isFinite(fileId)) { + throw new Error(`invalid SimpleX file id: ${fileId}`); } - return `#${raw}`; -} - -function formatSearchArg(search?: string | null): string { - const trimmed = search?.trim(); - if (!trimmed) { - return ""; - } - if (hasWhitespace(trimmed)) { - return `'${trimmed.replaceAll("'", "\\'")}'`; - } - return trimmed; + return `/fcancel ${Math.trunc(fileId)}`; } export function buildListUsersCommand(): string { @@ -279,8 +284,7 @@ export function buildShowActiveUserCommand(): string { } export function buildListContactsCommand(userId: number | string): string { - const id = normalizeCommandId(userId); - return `/_contacts ${id}`; + return `/_contacts ${normalizeCommandId(userId)}`; } export function buildListGroupsCommand(params: { @@ -291,8 +295,7 @@ export function buildListGroupsCommand(params: { const userId = normalizeCommandId(params.userId); const contactRef = params.contactId ? normalizeContactRef(params.contactId) : ""; const search = formatSearchArg(params.search); - const parts = ["/_groups", userId, contactRef, search].filter(Boolean); - return parts.join(" "); + return ["/_groups", userId, contactRef, search].filter(Boolean).join(" "); } export function buildListGroupMembersCommand(params: { @@ -301,8 +304,7 @@ export function buildListGroupMembersCommand(params: { }): string { const groupRef = normalizeGroupRef(params.groupId); const search = formatSearchArg(params.search); - const parts = ["/_members", groupRef, search].filter(Boolean); - return parts.join(" "); + return ["/_members", groupRef, search].filter(Boolean).join(" "); } export function buildAddGroupMemberCommand(params: { @@ -325,7 +327,42 @@ export function buildLeaveGroupCommand(groupId: number | string): string { export function buildUpdateGroupProfileCommand(params: { groupId: number | string; - profile: Record; + profile: Partial; }): string { return `/_group_profile ${normalizeGroupRef(params.groupId)} ${JSON.stringify(params.profile)}`; } + +export function buildCreateGroupCommand(profile: SimplexGroupProfile): string { + return `/_group json ${JSON.stringify(profile)}`; +} + +export function buildCreateGroupLinkCommand(params: { + groupId: number | string; + role: string; +}): string { + return `/_create link ${normalizeGroupRef(params.groupId)} ${params.role}`; +} + +export function buildShowGroupLinkCommand(groupId: number | string): string { + return `/_show link ${normalizeGroupRef(groupId)}`; +} + +export function buildDeleteGroupLinkCommand(groupId: number | string): string { + return `/_delete link ${normalizeGroupRef(groupId)}`; +} + +export function buildAcceptContactRequestCommand(contactRequestId: number | string): string { + return `/_accept ${normalizeCommandId(contactRequestId)}`; +} + +export function buildRejectContactRequestCommand(contactRequestId: number | string): string { + return `/_reject ${normalizeCommandId(contactRequestId)}`; +} + +export function buildConnectPlanCommand(link: string): string { + return `/connect plan ${quoteCliArg(link)}`; +} + +export function buildConnectCommand(link: string): string { + return `/connect ${quoteCliArg(link)}`; +} diff --git a/src/simplex/runtime/db-path.test.ts b/src/simplex/runtime/db-path.test.ts new file mode 100644 index 0000000..b2a7c18 --- /dev/null +++ b/src/simplex/runtime/db-path.test.ts @@ -0,0 +1,25 @@ +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { resolveSimplexCliDefaultDbPrefix, resolveSimplexDbFilePrefix } from "./db-path.js"; + +describe("simplex database path helpers", () => { + it("uses the SimpleX CLI default on unix platforms", () => { + expect(resolveSimplexCliDefaultDbPrefix({ platform: "linux" })).toBe("~/.simplex/simplex_v1"); + expect(resolveSimplexCliDefaultDbPrefix({ platform: "darwin" })).toBe("~/.simplex/simplex_v1"); + }); + + it("uses AppData for the SimpleX CLI default on Windows", () => { + expect( + resolveSimplexCliDefaultDbPrefix({ + platform: "win32", + env: { APPDATA: "C:\\Users\\alice\\AppData\\Roaming" }, + }) + ).toBe(path.join("C:\\Users\\alice\\AppData\\Roaming", "simplex", "simplex_v1")); + }); + + it("expands home-relative db prefixes", () => { + expect(resolveSimplexDbFilePrefix("~/.simplex/simplex_v1")).toContain( + path.join(".simplex", "simplex_v1") + ); + }); +}); diff --git a/src/simplex/runtime/db-path.ts b/src/simplex/runtime/db-path.ts new file mode 100644 index 0000000..615da54 --- /dev/null +++ b/src/simplex/runtime/db-path.ts @@ -0,0 +1,45 @@ +import os from "node:os"; +import path from "node:path"; + +type SimplexDbPathPlatform = NodeJS.Platform; + +export function resolveSimplexCliDefaultDbPrefix( + params: { platform?: SimplexDbPathPlatform; env?: NodeJS.ProcessEnv } = {} +): string { + const platform = params.platform ?? process.platform; + const env = params.env ?? process.env; + if (platform === "win32") { + return path.join(resolveWindowsAppData(env), "simplex", "simplex_v1"); + } + return "~/.simplex/simplex_v1"; +} + +export function resolveSimplexDbFilePrefix(value: string): string { + const expanded = expandHome(value); + if (process.platform === "win32") { + return path.resolve(expandWindowsEnvPrefix(expanded, process.env)); + } + return path.resolve(expanded); +} + +function resolveWindowsAppData(env: NodeJS.ProcessEnv): string { + return env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"); +} + +function expandHome(value: string): string { + if (value === "~") { + return os.homedir(); + } + if (value.startsWith("~/") || value.startsWith("~\\")) { + return path.join(os.homedir(), value.slice(2)); + } + return value; +} + +function expandWindowsEnvPrefix(value: string, env: NodeJS.ProcessEnv): string { + return value.replace(/^%([^%]+)%[/\\]?/, (_match, rawName: string) => { + const name = rawName.toUpperCase(); + const resolved = env[name] ?? env[rawName]; + return resolved ? `${resolved}${path.sep}` : ""; + }); +} diff --git a/src/simplex/simplex-errors.ts b/src/simplex/runtime/errors.ts similarity index 100% rename from src/simplex/simplex-errors.ts rename to src/simplex/runtime/errors.ts diff --git a/src/simplex/simplex-links.ts b/src/simplex/runtime/links.ts similarity index 85% rename from src/simplex/simplex-links.ts rename to src/simplex/runtime/links.ts index 861c338..30f6765 100644 --- a/src/simplex/simplex-links.ts +++ b/src/simplex/runtime/links.ts @@ -25,12 +25,10 @@ export function extractSimplexLink(resp: unknown): string | null { for (const str of strings) { for (const match of str.matchAll(SIMPLEX_LINK_REGEX)) { const raw = match[0]; - const cleaned = raw.replace(/[),.\]]+$/g, ""); - matches.push(cleaned); + matches.push(raw.replace(/[),.\]]+$/g, "")); } } - const preferred = matches.find((entry) => /simplex/i.test(entry)); - return preferred ?? matches[0] ?? null; + return matches.find((entry) => /simplex/i.test(entry)) ?? matches[0] ?? null; } export function extractSimplexLinks(resp: unknown): string[] { @@ -39,8 +37,7 @@ export function extractSimplexLinks(resp: unknown): string[] { const matches = new Set(); for (const str of strings) { for (const match of str.matchAll(SIMPLEX_LINK_REGEX)) { - const raw = match[0]; - const cleaned = raw.replace(/[),.\]]+$/g, ""); + const cleaned = match[0].replace(/[),.\]]+$/g, ""); if (cleaned) { matches.add(cleaned); } diff --git a/src/simplex/runtime/security.ts b/src/simplex/runtime/security.ts new file mode 100644 index 0000000..c6c1d2c --- /dev/null +++ b/src/simplex/runtime/security.ts @@ -0,0 +1,97 @@ +const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]); +const UNSAFE_BIND_HOSTS = new Set(["0.0.0.0", "::", "[::]"]); + +export type SimplexWsEndpointSecurity = { + valid: boolean; + redactedUrl: string; + warnings: string[]; + blockingWarnings: string[]; +}; + +export function redactSimplexWsUrl(rawUrl: string): string { + const trimmed = rawUrl.trim(); + if (!trimmed) { + return ""; + } + try { + const url = new URL(trimmed); + const auth = + url.username || url.password + ? `${url.username ? "redacted" : ""}${url.password ? ":redacted" : ""}@` + : ""; + const host = url.host; + const path = url.pathname === "/" && !trimmed.includes(`${host}/`) ? "" : url.pathname; + const query = url.search ? "?redacted" : ""; + return `${url.protocol}//${auth}${host}${path}${query}${url.hash ? "#redacted" : ""}`; + } catch { + return trimmed.replace(/([?&](?:token|secret|password|key|auth)=)[^&\s]+/gi, "$1redacted"); + } +} + +export function isSimplexLoopbackHost(hostname: string): boolean { + return LOOPBACK_HOSTS.has(hostname.trim().toLowerCase()); +} + +export function isSimplexUnsafeBindHost(hostname: string): boolean { + return UNSAFE_BIND_HOSTS.has(hostname.trim().toLowerCase()); +} + +export function describeSimplexWsEndpointSecurity( + rawUrl: string, + options: { allowUnsafeRemoteWs?: boolean } = {} +): SimplexWsEndpointSecurity { + const redactedUrl = redactSimplexWsUrl(rawUrl); + const warnings: string[] = []; + const blockingWarnings: string[] = []; + let parsed: URL; + + try { + parsed = new URL(rawUrl); + } catch { + const warning = `connection.wsUrl="${redactedUrl}" is not a valid URL.`; + return { + valid: false, + redactedUrl, + warnings: [warning], + blockingWarnings: [warning], + }; + } + + if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") { + const warning = `connection.wsUrl="${redactedUrl}" must start with ws:// or wss://.`; + warnings.push(warning); + blockingWarnings.push(warning); + } + + const hostname = parsed.hostname; + if (isSimplexUnsafeBindHost(hostname)) { + const warning = `connection.wsUrl="${redactedUrl}" targets ${hostname}. Bind simplex-chat to 127.0.0.1 or protect it behind a private network boundary.`; + warnings.push(warning); + blockingWarnings.push(warning); + } + + const loopback = isSimplexLoopbackHost(hostname); + if (parsed.protocol === "ws:" && !loopback) { + const warning = `connection.wsUrl="${redactedUrl}" uses plaintext WebSocket to a non-loopback host. Prefer ws://127.0.0.1, a private sidecar network, or wss:// with network access controls.`; + warnings.push(warning); + if (!options.allowUnsafeRemoteWs) { + blockingWarnings.push( + `${warning} Set connection.allowUnsafeRemoteWs=true only when this endpoint is protected by a private network, firewall, or authenticated TLS proxy.` + ); + } + } + + return { valid: blockingWarnings.length === 0, redactedUrl, warnings, blockingWarnings }; +} + +export function assertSimplexWsEndpointAllowed(params: { + wsUrl: string; + allowUnsafeRemoteWs?: boolean; +}): void { + const security = describeSimplexWsEndpointSecurity(params.wsUrl, { + allowUnsafeRemoteWs: params.allowUnsafeRemoteWs, + }); + if (security.blockingWarnings.length > 0) { + throw new Error(`Unsafe SimpleX WebSocket endpoint: ${security.blockingWarnings.join(" ")}`); + } +} diff --git a/src/simplex/runtime/transport.test.ts b/src/simplex/runtime/transport.test.ts new file mode 100644 index 0000000..b26e8fb --- /dev/null +++ b/src/simplex/runtime/transport.test.ts @@ -0,0 +1,155 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { ResolvedSimplexAccount } from "../../types/config.js"; + +const clientMock = vi.hoisted(() => ({ + constructed: 0, + instances: [] as Array<{ + connected: number; + closed: number; + }>, + reset() { + this.constructed = 0; + this.instances.length = 0; + }, +})); + +vi.mock("./client.js", () => ({ + SimplexClient: class { + source = "constructed"; + state = { connected: 0, closed: 0 }; + constructor() { + clientMock.constructed += 1; + clientMock.instances.push(this.state); + } + async connect() { + this.state.connected += 1; + } + async close() { + this.state.closed += 1; + } + }, +})); + +import type { SimplexClient } from "./client.js"; +import { + getActiveSimplexClient, + registerActiveSimplexClient, + unregisterActiveSimplexClient, + withSimplexClient, +} from "./transport.js"; + +const registeredAccounts: Array<{ account: ResolvedSimplexAccount; client: SimplexClient }> = []; + +function account( + accountId = "default", + wsUrl = `ws://127.0.0.1:5225/${accountId}` +): ResolvedSimplexAccount { + return { + accountId, + name: accountId, + enabled: true, + configured: true, + mode: "external", + wsUrl, + wsHost: "127.0.0.1", + wsPort: 5225, + config: { + connection: { wsUrl }, + }, + }; +} + +function activeClient(source: string): SimplexClient { + return { + source, + async connect() {}, + async close() {}, + } as unknown as SimplexClient; +} + +describe("simplex runtime transport", () => { + afterEach(() => { + for (const registered of registeredAccounts) { + unregisterActiveSimplexClient(registered.account, registered.client); + } + registeredAccounts.length = 0; + clientMock.reset(); + }); + + it("prefers the active monitor client for runtime calls", async () => { + const cfg = account("alpha"); + const client = activeClient("active"); + registeredAccounts.push({ account: cfg, client }); + await registerActiveSimplexClient(cfg, client); + + const result = await withSimplexClient({ + account: cfg, + run: async (runtimeClient) => (runtimeClient as unknown as { source: string }).source, + }); + + expect(result).toBe("active"); + expect(clientMock.constructed).toBe(0); + }); + + it("reuses an active monitor client for another account with the same WebSocket URL", async () => { + const wsUrl = "ws://127.0.0.1:5225"; + const monitored = account("alpha", wsUrl); + const alias = account("alias", wsUrl); + const client = activeClient("active"); + registeredAccounts.push({ account: monitored, client }); + await registerActiveSimplexClient(monitored, client); + + const result = await withSimplexClient({ + account: alias, + run: async (runtimeClient) => (runtimeClient as unknown as { source: string }).source, + }); + + expect(result).toBe("active"); + expect(clientMock.constructed).toBe(0); + }); + + it("does not unregister a newer active client with an older handle", async () => { + const cfg = account("gamma"); + const oldClient = activeClient("old"); + const newClient = activeClient("new"); + registeredAccounts.push( + { account: cfg, client: oldClient }, + { account: cfg, client: newClient } + ); + await registerActiveSimplexClient(cfg, oldClient); + await registerActiveSimplexClient(cfg, newClient); + + unregisterActiveSimplexClient(cfg, oldClient); + + expect(getActiveSimplexClient("gamma")).toBe(newClient); + }); + + it("closes transient clients created for one-shot runtime calls", async () => { + const cfg = account("delta"); + + const result = await withSimplexClient({ + account: cfg, + run: async (runtimeClient) => (runtimeClient as unknown as { source: string }).source, + }); + + expect(result).toBe("constructed"); + expect(clientMock.constructed).toBe(1); + expect(clientMock.instances[0]).toMatchObject({ connected: 1, closed: 1 }); + }); + + it("closes transient clients when the runtime call fails", async () => { + const cfg = account("epsilon"); + + await expect( + withSimplexClient({ + account: cfg, + run: async () => { + throw new Error("runtime command failed"); + }, + }) + ).rejects.toThrow("runtime command failed"); + + expect(clientMock.constructed).toBe(1); + expect(clientMock.instances[0]).toMatchObject({ connected: 1, closed: 1 }); + }); +}); diff --git a/src/simplex/runtime/transport.ts b/src/simplex/runtime/transport.ts new file mode 100644 index 0000000..be0d80a --- /dev/null +++ b/src/simplex/runtime/transport.ts @@ -0,0 +1,65 @@ +import type { ResolvedSimplexAccount } from "../../types/config.js"; +import type { SimplexLogger } from "../../types/simplex.js"; +import { SimplexClient } from "./client.js"; + +type SharedSimplexClientKey = `${string}|${number}`; + +const activeSimplexClients = new Map(); +const activeSimplexClientsByKey = new Map(); + +function sharedClientKey(account: ResolvedSimplexAccount): SharedSimplexClientKey { + const timeoutMs = account.config.connection?.connectTimeoutMs ?? 15_000; + return `${account.wsUrl}|${timeoutMs}`; +} + +export async function registerActiveSimplexClient( + account: ResolvedSimplexAccount, + client: SimplexClient +): Promise { + const key = sharedClientKey(account); + activeSimplexClients.set(account.accountId, client); + activeSimplexClientsByKey.set(key, client); +} + +export function unregisterActiveSimplexClient( + account: ResolvedSimplexAccount, + client: SimplexClient +): void { + if (activeSimplexClients.get(account.accountId) === client) { + activeSimplexClients.delete(account.accountId); + } + const key = sharedClientKey(account); + if (activeSimplexClientsByKey.get(key) === client) { + activeSimplexClientsByKey.delete(key); + } +} + +export function getActiveSimplexClient(accountId: string): SimplexClient | undefined { + return activeSimplexClients.get(accountId); +} + +export function hasActiveSimplexClient(accountId: string): boolean { + return activeSimplexClients.has(accountId); +} + +export async function withSimplexClient(params: { + account: ResolvedSimplexAccount; + logger?: SimplexLogger; + run: (client: SimplexClient) => Promise; +}): Promise { + const activeClient = activeSimplexClients.get(params.account.accountId); + if (activeClient) { + return await params.run(activeClient); + } + const sharedActiveClient = activeSimplexClientsByKey.get(sharedClientKey(params.account)); + if (sharedActiveClient) { + return await params.run(sharedActiveClient); + } + const client = new SimplexClient({ account: params.account, logger: params.logger }); + try { + await client.connect(); + return await params.run(client); + } finally { + await client.close().catch(() => undefined); + } +} diff --git a/src/simplex/simplex-ws-client.ts b/src/simplex/runtime/ws-client.ts similarity index 67% rename from src/simplex/simplex-ws-client.ts rename to src/simplex/runtime/ws-client.ts index 23d52c2..283bff5 100644 --- a/src/simplex/simplex-ws-client.ts +++ b/src/simplex/runtime/ws-client.ts @@ -1,40 +1,27 @@ import { randomUUID } from "node:crypto"; import WebSocket from "ws"; +import type { + SimplexLogger, + SimplexRuntimeEvent, + SimplexRuntimeResponse, +} from "../../types/simplex.js"; -export type SimplexWsResponse = { - corrId?: string; - resp?: { - type: string; - [key: string]: unknown; - }; - [key: string]: unknown; -}; - -export type SimplexWsEvent = { - type: string; - [key: string]: unknown; -}; - -export type SimplexWsClientOptions = { - url: string; - connectTimeoutMs?: number; - logger?: { - info?: (message: string) => void; - warn?: (message: string) => void; - error?: (message: string) => void; - debug?: (message: string) => void; - }; -}; - -export type SimplexWsConnectionState = { +export type SimplexConnectionState = { connected: boolean; at: number; expected?: boolean; error?: string | null; }; +type SimplexWsClientOptions = { + url: string; + connectTimeoutMs?: number; + maxPayloadBytes?: number; + logger?: SimplexLogger; +}; + type PendingCommand = { - resolve: (value: SimplexWsResponse) => void; + resolve: (value: SimplexRuntimeResponse) => void; reject: (err: Error) => void; timeout: NodeJS.Timeout; }; @@ -42,34 +29,46 @@ type PendingCommand = { export class SimplexWsClient { private readonly url: string; private readonly connectTimeoutMs: number; - private readonly logger?: SimplexWsClientOptions["logger"]; + private readonly maxPayloadBytes: number; + private readonly logger?: SimplexLogger; private ws: WebSocket | null = null; private connectPromise: Promise | null = null; private pending = new Map(); - private eventHandlers = new Set<(event: SimplexWsEvent) => void>(); - private connectionHandlers = new Set<(state: SimplexWsConnectionState) => void>(); + private eventHandlers = new Set<(event: SimplexRuntimeEvent) => void>(); + private connectionHandlers = new Set<(state: SimplexConnectionState) => void>(); + private lastConnectionState: SimplexConnectionState = { + connected: false, + at: Date.now(), + expected: true, + error: null, + }; private closing = false; constructor(options: SimplexWsClientOptions) { this.url = options.url; this.connectTimeoutMs = options.connectTimeoutMs ?? 15_000; + this.maxPayloadBytes = options.maxPayloadBytes ?? 16 * 1024 * 1024; this.logger = options.logger; } - onEvent(handler: (event: SimplexWsEvent) => void): () => void { + onEvent(handler: (event: SimplexRuntimeEvent) => void): () => void { this.eventHandlers.add(handler); return () => { this.eventHandlers.delete(handler); }; } - onConnectionState(handler: (state: SimplexWsConnectionState) => void): () => void { + onConnectionState(handler: (state: SimplexConnectionState) => void): () => void { this.connectionHandlers.add(handler); return () => { this.connectionHandlers.delete(handler); }; } + getConnectionState(): SimplexConnectionState { + return { ...this.lastConnectionState }; + } + async connect(): Promise { if (this.ws && this.ws.readyState === WebSocket.OPEN) { return; @@ -83,22 +82,19 @@ export class SimplexWsClient { let opened = false; const settleResolve = () => { - if (settled) { - return; + if (!settled) { + settled = true; + resolve(); } - settled = true; - resolve(); }; - const settleReject = (error: Error) => { - if (settled) { - return; + if (!settled) { + settled = true; + reject(error); } - settled = true; - reject(error); }; - const ws = new WebSocket(this.url); + const ws = new WebSocket(this.url, { maxPayload: this.maxPayloadBytes }); this.ws = ws; this.closing = false; @@ -125,11 +121,7 @@ export class SimplexWsClient { ws.on("close", (code, reason) => { clearTimeout(timeout); - this.logger?.warn?.("SimpleX WS closed"); - const closeReason = - typeof reason === "string" - ? reason - : Buffer.from(reason).toString("utf8") || "unknown reason"; + const closeReason = Buffer.from(reason).toString("utf8") || "unknown reason"; const closeError = new Error(`SimpleX WS closed (code=${code}, reason=${closeReason})`); this.handleSocketDisconnect(ws, closeError); if (!opened) { @@ -160,23 +152,43 @@ export class SimplexWsClient { async close(): Promise { this.rejectAllPending(new Error("SimpleX WS closed")); this.closing = true; - if (this.ws && this.ws.readyState === WebSocket.OPEN) { + const ws = this.ws; + if ( + ws && + (ws.readyState === WebSocket.CONNECTING || + ws.readyState === WebSocket.OPEN || + ws.readyState === WebSocket.CLOSING) + ) { await new Promise((resolve) => { - this.ws?.once("close", () => resolve()); - this.ws?.close(); + let settled = false; + const done = () => { + if (!settled) { + settled = true; + ws.off("close", done); + ws.off("error", done); + resolve(); + } + }; + ws.once("close", done); + ws.once("error", done); + if (ws.readyState === WebSocket.OPEN) { + ws.close(); + } else { + ws.terminate(); + } }); } this.ws = null; this.closing = false; } - async sendCommand(cmd: string, timeoutMs = 20_000): Promise { + async sendCommand(cmd: string, timeoutMs = 20_000): Promise { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { await this.connect(); } const corrId = randomUUID(); const payload = JSON.stringify({ corrId, cmd }); - return await new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { const timeout = setTimeout(() => { this.pending.delete(corrId); reject( @@ -195,24 +207,14 @@ export class SimplexWsClient { } private handleMessage(raw: WebSocket.RawData): void { - let text: string | null = null; - if (typeof raw === "string") { - text = raw; - } else if (raw instanceof Buffer) { - text = raw.toString("utf8"); - } else if (raw instanceof ArrayBuffer) { - text = Buffer.from(raw).toString("utf8"); - } else if (Array.isArray(raw)) { - text = Buffer.concat(raw).toString("utf8"); - } - + const text = rawToString(raw); if (!text) { this.logger?.warn?.("SimpleX WS message had unsupported payload type"); return; } - let parsed: SimplexWsResponse; + let parsed: SimplexRuntimeResponse; try { - parsed = JSON.parse(text) as SimplexWsResponse; + parsed = JSON.parse(text) as SimplexRuntimeResponse; } catch (err) { this.logger?.warn?.(`SimpleX WS parse error: ${String(err)}`); return; @@ -236,7 +238,7 @@ export class SimplexWsClient { } for (const handler of this.eventHandlers) { - handler(event as SimplexWsEvent); + handler(event as SimplexRuntimeEvent); } } @@ -249,7 +251,6 @@ export class SimplexWsClient { } private handleSocketDisconnect(ws: WebSocket, error: Error): void { - // Ignore stale events from an older socket after reconnect. if (this.ws !== ws) { return; } @@ -264,18 +265,31 @@ export class SimplexWsClient { }); } - private emitConnectionState(state: SimplexWsConnectionState): void { + private emitConnectionState(state: SimplexConnectionState): void { + this.lastConnectionState = { ...state }; for (const handler of this.connectionHandlers) { handler(state); } } } -function summarizeCommand(cmd: string): string { - const trimmed = cmd.trim(); - if (!trimmed) { - return "unknown command"; +function rawToString(raw: WebSocket.RawData): string | null { + if (typeof raw === "string") { + return raw; + } + if (raw instanceof Buffer) { + return raw.toString("utf8"); + } + if (raw instanceof ArrayBuffer) { + return Buffer.from(raw).toString("utf8"); + } + if (Array.isArray(raw)) { + return Buffer.concat(raw).toString("utf8"); } - const firstToken = trimmed.split(/\s+/, 1)[0]; + return null; +} + +function summarizeCommand(cmd: string): string { + const firstToken = cmd.trim().split(/\s+/, 1)[0]; return firstToken || "unknown command"; } diff --git a/src/simplex/services/connect-links.ts b/src/simplex/services/connect-links.ts new file mode 100644 index 0000000..ad0eb82 --- /dev/null +++ b/src/simplex/services/connect-links.ts @@ -0,0 +1,36 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { resolveRuntimeAccount, withActiveSimplexUser } from "../runtime/account.js"; + +export async function planSimplexConnectionLink(params: { + cfg: OpenClawConfig; + accountId?: string | null; + link: string; +}): Promise<{ accountId: string; plan: unknown; preparedLink: unknown }> { + const link = params.link.trim(); + if (!link) { + throw new Error("link is required"); + } + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const plan = await withActiveSimplexUser({ + account, + run: (_userId, client) => client.planConnect(link), + }); + return { accountId: account.accountId, plan, preparedLink: null }; +} + +export async function connectSimplexLink(params: { + cfg: OpenClawConfig; + accountId?: string | null; + link: string; +}): Promise<{ accountId: string; connected: boolean; result: unknown }> { + const link = params.link.trim(); + if (!link) { + throw new Error("link is required"); + } + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const result = await withActiveSimplexUser({ + account, + run: (_userId, client) => client.connectLink(link), + }); + return { accountId: account.accountId, connected: true, result }; +} diff --git a/src/simplex/services/contact-requests.ts b/src/simplex/services/contact-requests.ts new file mode 100644 index 0000000..8dcff9a --- /dev/null +++ b/src/simplex/services/contact-requests.ts @@ -0,0 +1,66 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { resolveRuntimeAccount, withActiveSimplexUser } from "../runtime/account.js"; +import { + deleteStoredSimplexContactRequest, + listStoredSimplexContactRequests, + type StoredSimplexContactRequest, +} from "../state/contact-requests.js"; + +export async function listSimplexContactRequests(params: { + cfg: OpenClawConfig; + accountId?: string | null; +}): Promise<{ accountId: string; requests: StoredSimplexContactRequest[] }> { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + return { + accountId: account.accountId, + requests: await listStoredSimplexContactRequests({ accountId: account.accountId }), + }; +} + +export async function acceptSimplexContactRequest(params: { + cfg: OpenClawConfig; + accountId?: string | null; + contactRequestId: number; +}): Promise<{ + accountId: string; + contactRequestId: number; + accepted: boolean; + contact: unknown; +}> { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const contact = await withActiveSimplexUser({ + account, + run: (_userId, client) => client.acceptContactRequest(params.contactRequestId), + }); + await deleteStoredSimplexContactRequest({ + accountId: account.accountId, + contactRequestId: params.contactRequestId, + }); + return { + accountId: account.accountId, + contactRequestId: params.contactRequestId, + accepted: true, + contact, + }; +} + +export async function rejectSimplexContactRequest(params: { + cfg: OpenClawConfig; + accountId?: string | null; + contactRequestId: number; +}): Promise<{ accountId: string; contactRequestId: number; rejected: boolean }> { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + await withActiveSimplexUser({ + account, + run: (_userId, client) => client.rejectContactRequest(params.contactRequestId), + }); + await deleteStoredSimplexContactRequest({ + accountId: account.accountId, + contactRequestId: params.contactRequestId, + }); + return { + accountId: account.accountId, + contactRequestId: params.contactRequestId, + rejected: true, + }; +} diff --git a/src/simplex/services/groups.ts b/src/simplex/services/groups.ts new file mode 100644 index 0000000..e154938 --- /dev/null +++ b/src/simplex/services/groups.ts @@ -0,0 +1,117 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { SimplexGroupMemberRole, SimplexGroupProfile } from "../../types/simplex.js"; +import { resolveRuntimeAccount, withActiveSimplexUser } from "../runtime/account.js"; + +const GROUP_MEMBER_ROLES = new Set(["observer", "author", "member", "moderator", "admin", "owner"]); + +function readRole(value: unknown, fallback: string): SimplexGroupMemberRole { + const role = typeof value === "string" ? value.trim().toLowerCase() : fallback; + if (!GROUP_MEMBER_ROLES.has(role)) { + throw new Error(`role must be one of ${[...GROUP_MEMBER_ROLES].join(", ")}`); + } + return role as SimplexGroupMemberRole; +} + +function readGroupId(value: unknown): number { + const groupId = + typeof value === "number" + ? value + : typeof value === "string" && /^[0-9]+$/.test(value.trim()) + ? Number(value.trim()) + : NaN; + if (!Number.isInteger(groupId) || groupId <= 0) { + throw new Error("groupId must be a positive integer"); + } + return groupId; +} + +function linkToString(link: unknown): string | null { + if (typeof link === "string" && link.trim()) { + return link; + } + if (!link || typeof link !== "object") { + return null; + } + const record = link as Record; + const nested = (record.connLinkContact as Record | undefined) ?? record; + const short = nested.connShortLink; + const full = nested.connFullLink; + return typeof short === "string" && short + ? short + : typeof full === "string" && full + ? full + : null; +} + +export async function createSimplexGroup(params: { + cfg: OpenClawConfig; + accountId?: string | null; + displayName: string; + fullName?: string; + description?: string; +}): Promise<{ accountId: string; group: unknown }> { + const displayName = params.displayName.trim(); + if (!displayName) { + throw new Error("displayName is required"); + } + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const profile: SimplexGroupProfile = { + displayName, + fullName: params.fullName?.trim() ?? "", + description: params.description?.trim() || undefined, + }; + const group = await withActiveSimplexUser({ + account, + run: (_userId, client) => client.createGroup(profile), + }); + return { accountId: account.accountId, group }; +} + +export async function createSimplexGroupLink(params: { + cfg: OpenClawConfig; + accountId?: string | null; + groupId: unknown; + role?: unknown; +}): Promise<{ accountId: string; groupId: number; role: SimplexGroupMemberRole; link: string }> { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const groupId = readGroupId(params.groupId); + const role = readRole(params.role, "member"); + const result = await withActiveSimplexUser({ + account, + run: (_userId, client) => client.createGroupLink({ groupId, role }), + }); + return { accountId: account.accountId, groupId, role, link: result.link ?? "" }; +} + +export async function listSimplexGroupLink(params: { + cfg: OpenClawConfig; + accountId?: string | null; + groupId: unknown; +}): Promise<{ accountId: string; groupId: number; link: string | null; linkInfo: unknown }> { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const groupId = readGroupId(params.groupId); + const result = await withActiveSimplexUser({ + account, + run: (_userId, client) => client.getGroupLink(groupId), + }); + return { + accountId: account.accountId, + groupId, + link: linkToString(result.link), + linkInfo: result.response, + }; +} + +export async function revokeSimplexGroupLink(params: { + cfg: OpenClawConfig; + accountId?: string | null; + groupId: unknown; +}): Promise<{ accountId: string; groupId: number; revoked: boolean }> { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const groupId = readGroupId(params.groupId); + await withActiveSimplexUser({ + account, + run: (_userId, client) => client.deleteGroupLink(groupId), + }); + return { accountId: account.accountId, groupId, revoked: true }; +} diff --git a/src/simplex/services/invites.ts b/src/simplex/services/invites.ts new file mode 100644 index 0000000..3fde8e3 --- /dev/null +++ b/src/simplex/services/invites.ts @@ -0,0 +1,91 @@ +import type { + SimplexInviteCreateResult, + SimplexInviteListResult, + SimplexInviteMode, + SimplexInviteRevokeResult, + SimplexInviteServiceOptions, +} from "../../types/invite.js"; +import { resolveRuntimeAccount, withActiveSimplexUser } from "../runtime/account.js"; +import { extractSimplexLinks, extractSimplexPendingHints } from "../runtime/links.js"; + +export function resolveInviteMode(value: unknown): SimplexInviteMode | null { + if (value === "connect" || value === "address") { + return value; + } + return null; +} + +export async function createSimplexInvite( + params: SimplexInviteServiceOptions & { mode: SimplexInviteMode } +): Promise { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const result = await withActiveSimplexUser({ + account, + logger: params.logger, + run: async (_userId, client) => { + if (params.mode === "connect") { + return await client.createInviteLink(); + } + return await client.createAddress(); + }, + }); + return { + accountId: account.accountId, + operation: params.mode === "connect" ? "create-link" : "create-address", + mode: params.mode, + link: result.link, + }; +} + +export async function listSimplexInvites( + params: SimplexInviteServiceOptions +): Promise { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const { addressResponse, contactsResponse, addressLink } = await withActiveSimplexUser({ + account, + logger: params.logger, + run: async (userId, client) => { + const [address, contacts] = await Promise.all([ + client.getAddress(), + client.listContacts(userId), + ]); + return { + addressResponse: address.response, + contactsResponse: contacts, + addressLink: address.link, + }; + }, + }); + return { + accountId: account.accountId, + addressLink, + links: [ + ...new Set([ + ...(addressLink ? [addressLink] : []), + ...extractSimplexLinks(addressResponse), + ...extractSimplexLinks(contactsResponse), + ]), + ], + pendingHints: extractSimplexPendingHints(contactsResponse), + addressResponse, + contactsResponse, + }; +} + +export async function revokeSimplexInvite( + params: SimplexInviteServiceOptions +): Promise { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const revoked = await withActiveSimplexUser({ + account, + logger: params.logger, + run: async (_userId, api) => { + await api.deleteAddress(); + return true; + }, + }); + return { + accountId: account.accountId, + revoked, + }; +} diff --git a/src/simplex/services/runtime-status.ts b/src/simplex/services/runtime-status.ts new file mode 100644 index 0000000..9a4d05a --- /dev/null +++ b/src/simplex/services/runtime-status.ts @@ -0,0 +1,93 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import { + readSimplexRuntimeVersion, + resolveRuntimeAccount, + withActiveSimplexUser, +} from "../runtime/account.js"; +import { getActiveSimplexClient } from "../runtime/transport.js"; + +export type SimplexRuntimeStatusResult = { + accountId: string; + enabled: boolean; + configured: boolean; + mode: string; + wsUrl: string | null; + runtimeVersion: string | null; + runtime: { + activeClient: boolean; + connected: boolean; + lastStateAt: number | null; + expectedDisconnect: boolean | null; + lastError: string | null; + }; + activeUser: unknown; + address: unknown; + counts: { + contacts: number; + groups: number; + users: number; + }; +}; + +export async function getSimplexRuntimeStatus(params: { + cfg: OpenClawConfig; + accountId?: string | null; +}): Promise { + const account = resolveRuntimeAccount(params.cfg, params.accountId); + const activeClient = getActiveSimplexClient(account.accountId); + const connection = activeClient?.getConnectionState(); + const details = await withActiveSimplexUser({ + account, + run: async (userId, client) => { + const [activeUser, address, contacts, groups, users] = await Promise.all([ + client.getActiveUser(), + client.getAddress().catch(() => undefined), + client.listContacts(userId).catch(() => []), + client.listGroups({ userId }).catch(() => []), + client.listUsers().catch(() => []), + ]); + return { activeUser, address, contacts, groups, users }; + }, + }); + + return { + accountId: account.accountId, + enabled: account.enabled, + configured: account.configured, + mode: account.mode, + wsUrl: account.wsUrl ?? null, + runtimeVersion: readSimplexRuntimeVersion(), + runtime: { + activeClient: Boolean(activeClient), + connected: connection?.connected ?? true, + lastStateAt: connection?.at ?? null, + expectedDisconnect: connection?.expected ?? null, + lastError: connection?.error ?? null, + }, + activeUser: details.activeUser ?? null, + address: details.address ?? null, + counts: { + contacts: details.contacts.length, + groups: details.groups.length, + users: details.users.length, + }, + }; +} + +export async function doctorSimplexRuntime(params: { + cfg: OpenClawConfig; + accountId?: string | null; +}): Promise { + const status = await getSimplexRuntimeStatus(params); + const issues: string[] = []; + if (!status.configured) { + issues.push("SimpleX account is not configured."); + } + if (!status.activeUser) { + issues.push("SimpleX runtime has no active user profile."); + } + if (status.runtime.lastError) { + issues.push(`SimpleX runtime error: ${status.runtime.lastError}`); + } + return { ...status, ok: issues.length === 0, issues }; +} diff --git a/src/simplex/simplex-commands.test.ts b/src/simplex/simplex-commands.test.ts deleted file mode 100644 index 6d08c3a..0000000 --- a/src/simplex/simplex-commands.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { describe, expect, it, test } from "vitest"; -import { - buildDeleteChatItemCommand, - buildReceiveFileCommand, - buildSendMessagesCommand, - buildUpdateGroupProfileCommand, - formatChatRef, -} from "./simplex-commands.js"; - -describe("simplex commands", () => { - it("rejects unsafe chat refs in send command", () => { - expect(() => - buildSendMessagesCommand({ - chatRef: "@123 ttl=on", - composedMessages: [], - }) - ).toThrow("invalid SimpleX chat ref"); - }); - - it("rejects unsafe chat item ids in delete command", () => { - expect(() => - buildDeleteChatItemCommand({ - chatRef: "@123", - chatItemIds: ["1,2"], - }) - ).toThrow("invalid SimpleX chat item id"); - }); - - it("quotes file paths for receive command", () => { - expect( - buildReceiveFileCommand({ - fileId: 7, - filePath: "/tmp/My File's Name.png", - }) - ).toBe("/freceive 7 '/tmp/My File\\'s Name.png'"); - }); - - it("emits raw JSON payload in send command", () => { - expect( - buildSendMessagesCommand({ - chatRef: "@123", - composedMessages: [ - { - msgContent: { - type: "text", - text: "hello world", - }, - }, - ], - }) - ).toBe('/_send @123 json [{"msgContent":{"type":"text","text":"hello world"}}]'); - }); - - it("normalizes scoped SimpleX group refs in send command", () => { - expect( - buildSendMessagesCommand({ - chatRef: "simplex:group:1", - composedMessages: [ - { - msgContent: { - type: "text", - text: "hello group", - }, - }, - ], - }) - ).toBe('/_send #1 json [{"msgContent":{"type":"text","text":"hello group"}}]'); - }); - - test.each([ - "simplex:4", - "simplex:contact:4", - "simplex:user:4", - "simplex:member:4", - ])("normalizes scoped SimpleX direct ref %s in delete command", (chatRef) => { - expect( - buildDeleteChatItemCommand({ - chatRef, - chatItemIds: [12], - }) - ).toBe("/_delete item @4 12 broadcast"); - }); - - it("emits raw JSON payload in update group profile command", () => { - expect( - buildUpdateGroupProfileCommand({ - groupId: "my-group", - profile: { displayName: "Team Room" }, - }) - ).toBe('/_group_profile #my-group {"displayName":"Team Room"}'); - }); - - it("rejects unsupported local/scoped chat refs", () => { - expect(() => formatChatRef({ type: "local", id: "abc" })).toThrow( - "local SimpleX chat refs are not supported" - ); - expect(() => formatChatRef({ type: "direct", id: "abc", scope: "team" })).toThrow( - "scoped SimpleX chat refs are not supported" - ); - }); -}); diff --git a/src/simplex/simplex-invite-service.ts b/src/simplex/simplex-invite-service.ts deleted file mode 100644 index dd92c50..0000000 --- a/src/simplex/simplex-invite-service.ts +++ /dev/null @@ -1,158 +0,0 @@ -import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; -import { resolveDefaultSimplexAccountId, resolveSimplexAccount } from "../config/accounts.js"; -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { INVITE_COMMANDS, type SimplexInviteMode } from "./simplex-invite.js"; -import { - extractSimplexLink, - extractSimplexLinks, - extractSimplexPendingHints, -} from "./simplex-links.js"; -import { - type SimplexLogger, - sendSimplexCommand, - sendSimplexCommandWithRetry, -} from "./simplex-transport.js"; -import type { SimplexWsResponse } from "./simplex-ws-client.js"; - -type RunningStateResolver = () => boolean; -type ChannelStarter = () => Promise; - -export type SimplexInviteServiceOptions = { - cfg: OpenClawConfig; - accountId?: string | null; - logger?: SimplexLogger; - startChannel?: ChannelStarter; - isRunning?: RunningStateResolver; -}; - -export type SimplexInviteCreateResult = { - accountId: string; - command: string; - mode: SimplexInviteMode; - link: string | null; - response: SimplexWsResponse; -}; - -export type SimplexInviteListResult = { - accountId: string; - addressLink: string | null; - links: string[]; - pendingHints: string[]; - addressResponse: SimplexWsResponse; - contactsResponse: SimplexWsResponse; -}; - -export type SimplexInviteRevokeResult = { - accountId: string; - response: SimplexWsResponse; -}; - -function resolveInviteAccount( - cfg: OpenClawConfig, - rawAccountId?: string | null -): ResolvedSimplexAccount { - const explicit = rawAccountId?.trim(); - const accountId = explicit || resolveDefaultSimplexAccountId(cfg); - const account = resolveSimplexAccount({ cfg, accountId }); - if (!account.enabled) { - throw new Error(`SimpleX account "${accountId}" is disabled`); - } - if (!account.configured) { - throw new Error(`SimpleX account "${accountId}" is not configured`); - } - return account; -} - -async function runInviteCommand( - account: ResolvedSimplexAccount, - params: { - command: string; - logger?: SimplexLogger; - startChannel?: ChannelStarter; - isRunning?: RunningStateResolver; - } -): Promise { - if (params.startChannel || params.isRunning) { - return await sendSimplexCommandWithRetry({ - account, - command: params.command, - logger: params.logger, - startChannel: params.startChannel, - isRunning: params.isRunning, - }); - } - return await sendSimplexCommand({ - account, - command: params.command, - logger: params.logger, - }); -} - -export async function createSimplexInvite( - params: SimplexInviteServiceOptions & { mode: SimplexInviteMode } -): Promise { - const account = resolveInviteAccount(params.cfg, params.accountId); - const command = INVITE_COMMANDS[params.mode]; - const response = await runInviteCommand(account, { - command, - logger: params.logger, - startChannel: params.startChannel, - isRunning: params.isRunning, - }); - return { - accountId: account.accountId, - command, - mode: params.mode, - link: extractSimplexLink(response), - response, - }; -} - -export async function listSimplexInvites( - params: SimplexInviteServiceOptions -): Promise { - const account = resolveInviteAccount(params.cfg, params.accountId); - const [addressResponse, contactsResponse] = await Promise.all([ - runInviteCommand(account, { - command: "/show_address", - logger: params.logger, - startChannel: params.startChannel, - isRunning: params.isRunning, - }), - runInviteCommand(account, { - command: "/contacts", - logger: params.logger, - startChannel: params.startChannel, - isRunning: params.isRunning, - }), - ]); - return { - accountId: account.accountId, - addressLink: extractSimplexLink(addressResponse), - links: [ - ...new Set([ - ...extractSimplexLinks(addressResponse), - ...extractSimplexLinks(contactsResponse), - ]), - ], - pendingHints: extractSimplexPendingHints(contactsResponse), - addressResponse, - contactsResponse, - }; -} - -export async function revokeSimplexInvite( - params: SimplexInviteServiceOptions -): Promise { - const account = resolveInviteAccount(params.cfg, params.accountId); - const response = await runInviteCommand(account, { - command: "/delete_address", - logger: params.logger, - startChannel: params.startChannel, - isRunning: params.isRunning, - }); - return { - accountId: account.accountId, - response, - }; -} diff --git a/src/simplex/simplex-invite.ts b/src/simplex/simplex-invite.ts deleted file mode 100644 index 0cf901d..0000000 --- a/src/simplex/simplex-invite.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type SimplexInviteMode = "connect" | "address"; - -export const INVITE_COMMANDS: Record = { - connect: "/c", - address: "/ad", -}; - -export function resolveInviteMode(value: unknown): SimplexInviteMode | null { - if (value === "connect" || value === "address") { - return value; - } - return null; -} diff --git a/src/simplex/simplex-transport.ts b/src/simplex/simplex-transport.ts deleted file mode 100644 index 3e84156..0000000 --- a/src/simplex/simplex-transport.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { ResolvedSimplexAccount } from "../config/types.js"; -import { SimplexWsClient, type SimplexWsResponse } from "./simplex-ws-client.js"; - -export type SimplexLogger = { - info?: (message: string) => void; - warn?: (message: string) => void; - error?: (message: string) => void; -}; - -type SharedSimplexClientKey = `${string}|${number}`; - -const sharedSimplexClients = new Map(); - -function getSharedSimplexClient(params: { - account: ResolvedSimplexAccount; - logger?: SimplexLogger; -}): SimplexWsClient { - const timeoutMs = params.account.config.connection?.connectTimeoutMs ?? 15_000; - const key: SharedSimplexClientKey = `${params.account.wsUrl}|${timeoutMs}`; - const existing = sharedSimplexClients.get(key); - if (existing) { - return existing; - } - const created = new SimplexWsClient({ - url: params.account.wsUrl, - connectTimeoutMs: timeoutMs, - logger: params.logger, - }); - sharedSimplexClients.set(key, created); - return created; -} - -export async function sendSimplexCommand(params: { - account: ResolvedSimplexAccount; - command: string; - logger?: SimplexLogger; -}): Promise { - const client = getSharedSimplexClient(params); - await client.connect(); - return await client.sendCommand(params.command); -} - -export async function sendSimplexCommandWithRetry(params: { - account: ResolvedSimplexAccount; - command: string; - logger?: SimplexLogger; - startChannel?: () => Promise; - isRunning?: () => boolean; -}): Promise { - const maxAttempts = 6; - let started = false; - let lastError: Error | null = null; - - for (let attempt = 0; attempt < maxAttempts; attempt += 1) { - try { - return await sendSimplexCommand(params); - } catch (err) { - lastError = err instanceof Error ? err : new Error(String(err)); - const running = params.isRunning?.() ?? false; - if (!started && !running && params.startChannel) { - started = true; - await params.startChannel(); - } - await new Promise((resolve) => setTimeout(resolve, 500 + attempt * 400)); - } - } - - throw lastError ?? new Error("SimpleX command failed"); -} diff --git a/src/simplex/simplex-ws-client.test.ts b/src/simplex/simplex-ws-client.test.ts deleted file mode 100644 index 6fb8e15..0000000 --- a/src/simplex/simplex-ws-client.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { WebSocketServer } from "ws"; -import { SimplexWsClient } from "./simplex-ws-client.js"; - -type ServerInfo = { - wss: WebSocketServer; - url: string; -}; - -let server: ServerInfo | null = null; - -function startServer(): Promise { - return new Promise((resolve, reject) => { - const wss = new WebSocketServer({ host: "127.0.0.1", port: 0 }); - const cleanup = () => { - wss.off("listening", onListening); - wss.off("error", onError); - }; - const onListening = () => { - cleanup(); - const address = wss.address(); - if (!address || typeof address === "string") { - reject(new Error("Failed to bind websocket server")); - return; - } - resolve({ wss, url: `ws://127.0.0.1:${address.port}` }); - }; - const onError = (err: Error) => { - cleanup(); - reject(err); - }; - - wss.on("listening", onListening); - wss.on("error", onError); - }); -} - -function stopServer(wss: WebSocketServer): Promise { - return new Promise((resolve) => wss.close(() => resolve())); -} - -describe("SimplexWsClient", () => { - beforeEach(async () => { - server = await startServer(); - }); - - afterEach(async () => { - if (server) { - await stopServer(server.wss); - server = null; - } - }); - - it("round-trips commands and emits events", async () => { - if (!server) { - throw new Error("server not initialized"); - } - const events: Array<{ type: string }> = []; - server.wss.on("connection", (socket) => { - socket.on("message", (data) => { - let text: string; - if (typeof data === "string") { - text = data; - } else if (Buffer.isBuffer(data)) { - text = data.toString("utf8"); - } else if (Array.isArray(data)) { - text = Buffer.concat(data).toString("utf8"); - } else { - text = Buffer.from(data).toString("utf8"); - } - const parsed = JSON.parse(text) as { corrId?: string }; - socket.send(Buffer.from(JSON.stringify({ corrId: parsed.corrId, resp: { type: "ok" } }))); - socket.send(JSON.stringify({ resp: { type: "event", hello: true } })); - }); - }); - - const client = new SimplexWsClient({ url: server.url }); - client.onEvent((event) => events.push(event)); - - const result = await client.sendCommand("/ping"); - expect(result.resp?.type).toBe("ok"); - - await new Promise((resolve) => setTimeout(resolve, 10)); - expect(events[0]?.type).toBe("event"); - - await client.close(); - }); - - it("rejects pending command on unexpected close and reconnects cleanly", async () => { - if (!server) { - throw new Error("server not initialized"); - } - - let connectionCount = 0; - server.wss.on("connection", (socket) => { - connectionCount += 1; - socket.on("message", (data) => { - let text: string; - if (typeof data === "string") { - text = data; - } else if (Buffer.isBuffer(data)) { - text = data.toString("utf8"); - } else if (Array.isArray(data)) { - text = Buffer.concat(data).toString("utf8"); - } else { - text = Buffer.from(data).toString("utf8"); - } - const parsed = JSON.parse(text) as { corrId?: string }; - - if (connectionCount === 1) { - socket.close(); - return; - } - socket.send(Buffer.from(JSON.stringify({ corrId: parsed.corrId, resp: { type: "ok" } }))); - }); - }); - - const client = new SimplexWsClient({ url: server.url }); - await expect(client.sendCommand("/first", 5_000)).rejects.toThrow("SimpleX WS closed"); - - const second = await client.sendCommand("/second"); - expect(second.resp?.type).toBe("ok"); - - await client.close(); - }); - - it("redacts timed out command payloads", async () => { - if (!server) { - throw new Error("server not initialized"); - } - - server.wss.on("connection", (socket) => { - socket.on("message", () => { - // Intentionally do not respond. - }); - }); - - const client = new SimplexWsClient({ url: server.url }); - await expect( - client.sendCommand( - '/_send @123 json [{"msgContent":{"type":"text","text":"secret body"}}]', - 25 - ) - ).rejects.toThrow("SimpleX command timeout after 25ms (/_send)"); - await expect( - client.sendCommand( - '/_send @123 json [{"msgContent":{"type":"text","text":"secret body"}}]', - 25 - ) - ).rejects.not.toThrow("secret body"); - - await client.close(); - }); -}); diff --git a/src/simplex/state/contact-requests.ts b/src/simplex/state/contact-requests.ts new file mode 100644 index 0000000..28d8965 --- /dev/null +++ b/src/simplex/state/contact-requests.ts @@ -0,0 +1,77 @@ +import type { PluginRuntime } from "openclaw/plugin-sdk/channel-core"; +import { getSimplexRuntime } from "../../channel/runtime.js"; +import { openSimplexKeyedStore } from "./keyed-store.js"; + +const REQUEST_STORE_NAMESPACE = "simplex-contact-requests"; +const REQUEST_STORE_MAX_ENTRIES = 500; +const REQUEST_STORE_TTL_MS = 30 * 24 * 60 * 60 * 1000; + +export type StoredSimplexContactRequest = { + accountId: string; + contactRequestId: number; + displayName?: string; + profile?: unknown; + createdAt?: string; + updatedAt?: string; + xContactId?: string; + storedAt: number; +}; + +function requestKey(accountId: string, contactRequestId: number): string { + return `${accountId}:${contactRequestId}`; +} + +function requestStore(runtime?: PluginRuntime | null) { + return openSimplexKeyedStore({ + runtime, + namespace: REQUEST_STORE_NAMESPACE, + maxEntries: REQUEST_STORE_MAX_ENTRIES, + defaultTtlMs: REQUEST_STORE_TTL_MS, + }); +} + +export async function recordSimplexContactRequest(params: { + accountId: string; + contactRequest: unknown; +}): Promise { + const request = params.contactRequest as Record; + const contactRequestId = request.contactRequestId; + if (typeof contactRequestId !== "number") { + return; + } + const stored: StoredSimplexContactRequest = { + accountId: params.accountId, + contactRequestId, + displayName: + typeof request.localDisplayName === "string" ? request.localDisplayName : undefined, + profile: request.profile, + createdAt: typeof request.createdAt === "string" ? request.createdAt : undefined, + updatedAt: typeof request.updatedAt === "string" ? request.updatedAt : undefined, + xContactId: typeof request.xContactId === "string" ? request.xContactId : undefined, + storedAt: Date.now(), + }; + await requestStore(getSimplexRuntime()).register( + requestKey(params.accountId, contactRequestId), + stored, + { ttlMs: REQUEST_STORE_TTL_MS } + ); +} + +export async function listStoredSimplexContactRequests(params: { + accountId: string; +}): Promise { + const entries = await requestStore(getSimplexRuntime()).entries(); + return entries + .map((entry) => entry.value) + .filter((request) => request.accountId === params.accountId) + .toSorted((a, b) => b.storedAt - a.storedAt); +} + +export async function deleteStoredSimplexContactRequest(params: { + accountId: string; + contactRequestId: number; +}): Promise { + return await requestStore(getSimplexRuntime()).delete( + requestKey(params.accountId, params.contactRequestId) + ); +} diff --git a/src/simplex/state/event-dedupe.ts b/src/simplex/state/event-dedupe.ts new file mode 100644 index 0000000..7e55764 --- /dev/null +++ b/src/simplex/state/event-dedupe.ts @@ -0,0 +1,26 @@ +import { getSimplexRuntime } from "../../channel/runtime.js"; +import { openSimplexKeyedStore } from "./keyed-store.js"; + +const SEEN_EVENT_NAMESPACE = "simplex-seen-events"; +const SEEN_EVENT_MAX_ENTRIES = 10_000; +const SEEN_EVENT_TTL_MS = 7 * 24 * 60 * 60 * 1000; + +export async function markSimplexEventSeen(params: { + accountId: string; + chatId: number | string; + messageId: number | string; +}): Promise { + const key = `${params.accountId}:${params.chatId}:${params.messageId}`; + const store = openSimplexKeyedStore({ + runtime: getSimplexRuntime(), + namespace: SEEN_EVENT_NAMESPACE, + maxEntries: SEEN_EVENT_MAX_ENTRIES, + defaultTtlMs: SEEN_EVENT_TTL_MS, + }); + const seen = await store.lookup(key); + if (seen) { + return false; + } + await store.register(key, true, { ttlMs: SEEN_EVENT_TTL_MS }); + return true; +} diff --git a/src/simplex/state/keyed-store.test.ts b/src/simplex/state/keyed-store.test.ts new file mode 100644 index 0000000..5774205 --- /dev/null +++ b/src/simplex/state/keyed-store.test.ts @@ -0,0 +1,55 @@ +import type { PluginRuntime } from "openclaw/plugin-sdk/channel-core"; +import { describe, expect, it } from "vitest"; +import { openSimplexKeyedStore } from "./keyed-store.js"; + +describe("simplex keyed store fallback", () => { + it("enforces maxEntries by evicting the oldest fallback entry", async () => { + const namespace = `test-max-${Date.now()}-${Math.random()}`; + const store = openSimplexKeyedStore({ + namespace, + maxEntries: 2, + }); + + await store.register("a", "one"); + await store.register("b", "two"); + await store.register("c", "three"); + + await expect(store.lookup("a")).resolves.toBeUndefined(); + await expect(store.lookup("b")).resolves.toBe("two"); + await expect(store.lookup("c")).resolves.toBe("three"); + }); + + it("applies default ttl when a register call does not specify ttl", async () => { + const namespace = `test-ttl-${Date.now()}-${Math.random()}`; + const store = openSimplexKeyedStore({ + namespace, + maxEntries: 10, + defaultTtlMs: 1, + }); + + await store.register("a", "one"); + await new Promise((resolve) => setTimeout(resolve, 5)); + + await expect(store.lookup("a")).resolves.toBeUndefined(); + }); + + it("falls back to memory when the host exposes but rejects keyed stores", async () => { + const namespace = `test-rejected-host-store-${Date.now()}-${Math.random()}`; + const store = openSimplexKeyedStore({ + runtime: { + state: { + resolveStateDir: () => "/tmp/openclaw-simplex-test", + openKeyedStore() { + throw new Error("openKeyedStore is only available for bundled plugins in this release"); + }, + }, + } as unknown as PluginRuntime, + namespace, + maxEntries: 10, + }); + + await store.register("a", "one"); + + await expect(store.lookup("a")).resolves.toBe("one"); + }); +}); diff --git a/src/simplex/state/keyed-store.ts b/src/simplex/state/keyed-store.ts new file mode 100644 index 0000000..1541c19 --- /dev/null +++ b/src/simplex/state/keyed-store.ts @@ -0,0 +1,77 @@ +import type { PluginRuntime } from "openclaw/plugin-sdk/channel-core"; + +const memoryStores = new Map>(); + +function memoryStore(params: { namespace: string; maxEntries: number; defaultTtlMs?: number }) { + const entries = + memoryStores.get(params.namespace) ?? new Map(); + memoryStores.set(params.namespace, entries); + function sweep(): void { + const now = Date.now(); + for (const [key, entry] of entries) { + if (entry.expiresAt && entry.expiresAt <= now) { + entries.delete(key); + } + } + } + function enforceMaxEntries(): void { + while (entries.size > params.maxEntries) { + const oldestKey = entries.keys().next().value as string | undefined; + if (!oldestKey) { + return; + } + entries.delete(oldestKey); + } + } + return { + async register(key: string, value: T, opts?: { ttlMs?: number }) { + sweep(); + const ttlMs = opts?.ttlMs ?? params.defaultTtlMs; + entries.delete(key); + entries.set(key, { + value, + expiresAt: ttlMs ? Date.now() + ttlMs : undefined, + }); + enforceMaxEntries(); + }, + async lookup(key: string): Promise { + sweep(); + return entries.get(key)?.value as T | undefined; + }, + async delete(key: string): Promise { + return entries.delete(key); + }, + async entries(): Promise< + Array<{ key: string; value: T; createdAt: number; expiresAt?: number }> + > { + sweep(); + return [...entries].map(([key, entry]) => ({ + key, + value: entry.value as T, + createdAt: 0, + expiresAt: entry.expiresAt, + })); + }, + }; +} + +export function openSimplexKeyedStore(params: { + runtime?: PluginRuntime | null; + namespace: string; + maxEntries: number; + defaultTtlMs?: number; +}) { + const state = params.runtime?.state; + if (state?.openKeyedStore) { + try { + return state.openKeyedStore({ + namespace: params.namespace, + maxEntries: params.maxEntries, + defaultTtlMs: params.defaultTtlMs, + }); + } catch { + return memoryStore(params); + } + } + return memoryStore(params); +} diff --git a/src/tools/plugin-tools.ts b/src/tools/plugin-tools.ts index 351c71c..929b20c 100644 --- a/src/tools/plugin-tools.ts +++ b/src/tools/plugin-tools.ts @@ -2,17 +2,13 @@ import { type Static, Type } from "@sinclair/typebox"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { executeSimplexAction } from "../actions/execute.js"; import { resolveDefaultSimplexAccountId } from "../config/accounts.js"; -import { resolveInviteMode } from "../simplex/simplex-invite.js"; import { createSimplexInvite, listSimplexInvites, + resolveInviteMode, revokeSimplexInvite, -} from "../simplex/simplex-invite-service.js"; - -type ToolResult = { - content: Array<{ type: "text"; text: string }>; - details: unknown; -}; +} from "../simplex/services/invites.js"; +import type { ToolResult } from "../types/actions.js"; const destructiveToolNames = new Set([ "simplex_invite_revoke", @@ -173,7 +169,7 @@ export function registerSimplexTools(api: OpenClawPluginApi): void { accountId: resolveToolAccountId(api, rawParams?.accountId, ctx.agentAccountId), logger: api.logger, }); - return jsonResult({ ...result, revoked: true }); + return jsonResult(result); }, }), { name: "simplex_invite_revoke" } diff --git a/src/actions/types.ts b/src/types/actions.ts similarity index 100% rename from src/actions/types.ts rename to src/types/actions.ts diff --git a/src/types/channel.ts b/src/types/channel.ts new file mode 100644 index 0000000..0a78888 --- /dev/null +++ b/src/types/channel.ts @@ -0,0 +1,6 @@ +export type SimplexExplicitTarget = { + to: string; + chatType: "direct" | "group"; +}; + +export type SimplexTargetKind = SimplexExplicitTarget["chatType"] | null; diff --git a/src/config/types.ts b/src/types/config.ts similarity index 67% rename from src/config/types.ts rename to src/types/config.ts index 91194e1..c71b31b 100644 --- a/src/config/types.ts +++ b/src/types/config.ts @@ -1,12 +1,11 @@ -import type { SimplexAccountConfig } from "./config-schema.js"; - -export type SimplexConnectionMode = "external"; +import type { SimplexAccountConfig } from "../config/config-schema.js"; export type SimplexConnectionConfig = { - mode?: SimplexConnectionMode; + mode?: "external"; wsUrl?: string; wsHost?: string; wsPort?: number; + allowUnsafeRemoteWs?: boolean; autoAcceptFiles?: boolean; connectTimeoutMs?: number; }; @@ -16,7 +15,7 @@ export type ResolvedSimplexAccount = { enabled: boolean; name?: string; configured: boolean; - mode: SimplexConnectionMode; + mode: "external"; wsUrl: string; wsHost: string; wsPort: number; diff --git a/src/types/events.ts b/src/types/events.ts new file mode 100644 index 0000000..20b9987 --- /dev/null +++ b/src/types/events.ts @@ -0,0 +1,34 @@ +export type SimplexChatItem = { + chatInfo?: { + type?: string; + contact?: { contactId?: number; localDisplayName?: string; profile?: { displayName?: string } }; + groupInfo?: { groupId?: number; localDisplayName?: string }; + }; + chatItem?: { + chatDir?: { + type?: string; + groupMember?: { + memberId?: string; + groupMemberId?: number; + contactId?: number | string; + localDisplayName?: string; + }; + }; + meta?: { itemId?: number; itemTs?: string }; + content?: { type?: string; msgContent?: { type?: string; text?: string } }; + file?: { + fileId?: number; + fileName?: string; + fileSize?: number; + fileSource?: { filePath?: string }; + }; + }; +}; + +export type SimplexChatContext = { + chatType: "direct" | "group"; + chatId: number; + chatLabel: string; + senderId?: string; + senderName?: string; +}; diff --git a/src/types/invite.ts b/src/types/invite.ts new file mode 100644 index 0000000..fd7523a --- /dev/null +++ b/src/types/invite.ts @@ -0,0 +1,31 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; +import type { SimplexLogger } from "./simplex.js"; + +export type SimplexInviteMode = "connect" | "address"; + +export type SimplexInviteServiceOptions = { + cfg: OpenClawConfig; + accountId?: string | null; + logger?: SimplexLogger; +}; + +export type SimplexInviteCreateResult = { + accountId: string; + operation: "create-link" | "create-address"; + mode: SimplexInviteMode; + link: string | null; +}; + +export type SimplexInviteListResult = { + accountId: string; + addressLink: string | null; + links: string[]; + pendingHints: string[]; + addressResponse: unknown; + contactsResponse: unknown; +}; + +export type SimplexInviteRevokeResult = { + accountId: string; + revoked: boolean; +}; diff --git a/src/types/security.ts b/src/types/security.ts new file mode 100644 index 0000000..f71800b --- /dev/null +++ b/src/types/security.ts @@ -0,0 +1,4 @@ +export type SimplexAllowlistEntry = { + kind: "any" | "sender" | "group"; + value: string; +}; diff --git a/src/types/simplex.ts b/src/types/simplex.ts new file mode 100644 index 0000000..5d66d8c --- /dev/null +++ b/src/types/simplex.ts @@ -0,0 +1,62 @@ +export type SimplexChatType = "direct" | "group" | "local"; +export type SimplexChatRef = { + type: SimplexChatType; + id: number | string; + scope?: string | null; +}; + +export type SimplexNumericChatRef = ["direct" | "group", number]; + +export type SimplexMsgContent = { + type: "text" | "link" | "image" | "video" | "voice" | "file" | "report" | "chat" | "unknown"; + text: string; + [key: string]: unknown; +}; + +export type SimplexComposedMessage = { + msgContent: SimplexMsgContent; + quotedItemId?: number; + fileSource?: { + filePath: string; + cryptoArgs?: { fileKey: string; fileNonce: string }; + }; + mentions?: Record; +}; + +export type SimplexDeleteMode = "broadcast" | "internal" | "internalMark"; +export type SimplexReaction = Record; +export type SimplexGroupMemberRole = + | "observer" + | "author" + | "member" + | "moderator" + | "admin" + | "owner"; +export type SimplexGroupProfile = { + displayName: string; + fullName?: string; + description?: string; +}; + +export type SimplexRuntimeResponse = { + corrId?: string; + resp?: { + type: string; + [key: string]: unknown; + }; + [key: string]: unknown; +}; + +export type SimplexRuntimeEvent = { + type: string; + [key: string]: unknown; +}; + +export type SimplexChatEvent = SimplexRuntimeEvent; + +export type SimplexLogger = { + info?: (message: string) => void; + warn?: (message: string) => void; + error?: (message: string) => void; + debug?: (message: string) => void; +}; diff --git a/tsconfig.json b/tsconfig.json index 1540401..3e2468e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, + "ignoreDeprecations": "6.0", "types": ["node", "vitest/globals"] }, "include": ["index.ts", "setup-entry.ts", "index.test.ts", "src/**/*.ts"]