From 6c3ba7dd1fb5793e1e2d87b90f7e92498ab0381d Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 13 Jan 2026 14:13:13 -0800 Subject: [PATCH] chore: refactor to use useStream --- package.json | 4 +- pnpm-lock.yaml | 865 ++++++++++++++++++ src/main/agent/runtime.ts | 27 +- src/main/db/index.ts | 16 +- src/main/ipc/agent.ts | 402 ++++---- src/preload/index.d.ts | 6 + src/preload/index.ts | 53 +- src/renderer/src/App.tsx | 23 +- .../src/components/chat/ChatContainer.tsx | 299 ++++-- .../src/components/sidebar/ThreadSidebar.tsx | 67 +- src/renderer/src/lib/electron-transport.ts | 267 ++++++ src/renderer/src/lib/store.ts | 281 ++---- src/types.ts | 64 ++ tsconfig.node.json | 5 +- tsconfig.web.json | 5 +- 15 files changed, 1818 insertions(+), 566 deletions(-) create mode 100644 src/renderer/src/lib/electron-transport.ts create mode 100644 src/types.ts diff --git a/package.json b/package.json index 9023e9a..6e0bc00 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@langchain/core": "^1.1.12", "@langchain/langgraph": "^1.0.15", "@langchain/langgraph-checkpoint": "^1.0.0", + "@langchain/langgraph-sdk": "^1.5.3", "@langchain/openai": "^1.2.1", "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", @@ -77,5 +78,6 @@ "tailwindcss": "^4.0.0", "typescript": "^5.9.3", "vite": "^7.2.6" - } + }, + "packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea31c0a..df941ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@langchain/langgraph-checkpoint': specifier: ^1.0.0 version: 1.0.0(@langchain/core@1.1.12(openai@6.16.0(zod@4.3.5))) + '@langchain/langgraph-sdk': + specifier: ^1.5.3 + version: 1.5.3(@langchain/core@1.1.12(openai@6.16.0(zod@4.3.5)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@langchain/openai': specifier: ^1.2.1 version: 1.2.1(@langchain/core@1.1.12(openai@6.16.0(zod@4.3.5))) @@ -74,9 +77,15 @@ importers: lucide-react: specifier: ^0.469.0 version: 0.469.0(react@19.2.3) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.8)(react@19.2.3) react-resizable-panels: specifier: ^4.4.0 version: 4.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 sql.js: specifier: ^1.12.0 version: 1.13.0 @@ -1519,12 +1528,18 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} @@ -1534,6 +1549,9 @@ packages: '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -1554,6 +1572,12 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} @@ -1622,6 +1646,9 @@ packages: resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react@5.1.2': resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1768,6 +1795,9 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1855,10 +1885,25 @@ packages: caniuse-lite@1.0.30001764: resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -1915,6 +1960,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} @@ -1985,6 +2033,9 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -2014,6 +2065,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2024,6 +2079,9 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} @@ -2177,6 +2235,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -2252,6 +2314,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2265,6 +2330,9 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} @@ -2494,6 +2562,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -2504,6 +2578,9 @@ packages: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -2554,6 +2631,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -2562,6 +2642,12 @@ packages: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2594,6 +2680,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2614,6 +2703,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -2642,6 +2734,10 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -2889,6 +2985,9 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2919,6 +3018,9 @@ packages: resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==} engines: {node: ^18.17.0 || >=20.5.0} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -2927,10 +3029,139 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3195,6 +3426,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -3285,6 +3519,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -3307,6 +3544,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} @@ -3367,6 +3610,18 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3547,6 +3802,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -3595,6 +3853,9 @@ packages: 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==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3607,6 +3868,12 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -3666,6 +3933,12 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} @@ -3728,6 +4001,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unique-filename@4.0.0: resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3736,6 +4012,21 @@ packages: resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==} engines: {node: ^18.17.0 || >=20.5.0} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -3795,6 +4086,12 @@ packages: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3949,6 +4246,9 @@ packages: use-sync-external-store: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: 7zip-bin@5.2.0: {} @@ -5192,12 +5492,20 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + '@types/estree@1.0.8': {} '@types/fs-extra@9.0.13': dependencies: '@types/node': 22.19.5 + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/http-cache-semantics@4.0.4': {} '@types/json-schema@7.0.15': {} @@ -5206,6 +5514,10 @@ snapshots: dependencies: '@types/node': 22.19.5 + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/ms@2.1.0': {} '@types/node@22.19.5': @@ -5230,6 +5542,10 @@ snapshots: dependencies: '@types/node': 22.19.5 + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@types/uuid@10.0.0': {} '@types/verror@1.10.11': @@ -5331,6 +5647,8 @@ snapshots: '@typescript-eslint/types': 8.53.0 eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: '@babel/core': 7.28.6 @@ -5517,6 +5835,8 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 + bail@2.0.2: {} + balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -5642,11 +5962,21 @@ snapshots: caniuse-lite@1.0.30001764: {} + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chownr@2.0.0: {} chownr@3.0.0: {} @@ -5695,6 +6025,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@5.1.0: {} commander@9.5.0: @@ -5770,6 +6102,10 @@ snapshots: decamelize@1.2.0: {} + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -5815,6 +6151,8 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -5822,6 +6160,10 @@ snapshots: detect-node@2.1.0: optional: true + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dir-compare@4.2.0: dependencies: minimatch: 3.1.2 @@ -6143,6 +6485,8 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -6259,6 +6603,8 @@ snapshots: estraverse@5.3.0: {} + estree-util-is-identifier-name@3.0.0: {} + esutils@2.0.3: {} eventemitter3@4.0.7: {} @@ -6267,6 +6613,8 @@ snapshots: exponential-backoff@3.1.3: {} + extend@3.0.2: {} + extract-zip@2.0.1: dependencies: debug: 4.4.3 @@ -6533,6 +6881,30 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -6543,6 +6915,8 @@ snapshots: dependencies: lru-cache: 6.0.0 + html-url-attributes@3.0.1: {} + http-cache-semantics@4.2.0: {} http-proxy-agent@7.0.2: @@ -6594,6 +6968,8 @@ snapshots: inherits@2.0.4: {} + inline-style-parser@0.2.7: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -6602,6 +6978,13 @@ snapshots: ip-address@10.1.0: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -6642,6 +7025,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -6662,6 +7047,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-interactive@1.0.0: {} is-map@2.0.3: {} @@ -6679,6 +7066,8 @@ snapshots: is-obj@2.0.0: {} + is-plain-obj@4.1.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6908,6 +7297,8 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -6948,6 +7339,8 @@ snapshots: transitivePeerDependencies: - supports-color + markdown-table@3.0.4: {} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -6955,8 +7348,352 @@ snapshots: math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + 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 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + 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.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + merge2@1.4.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -7212,6 +7949,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -7283,6 +8030,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.1.0: {} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -7301,6 +8050,24 @@ snapshots: react-is@16.13.1: {} + react-markdown@10.1.0(@types/react@19.2.8)(react@19.2.3): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.8 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.3 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-refresh@0.18.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3): @@ -7369,6 +8136,40 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.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-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + 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-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -7591,6 +8392,8 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + sprintf-js@1.1.3: optional: true @@ -7667,6 +8470,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -7677,6 +8485,14 @@ snapshots: strip-json-comments@3.1.1: {} + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + sumchecker@3.0.1: dependencies: debug: 4.4.3 @@ -7745,6 +8561,10 @@ snapshots: dependencies: is-number: 7.0.0 + trim-lines@3.0.1: {} + + trough@2.2.0: {} + truncate-utf8-bytes@1.0.2: dependencies: utf8-byte-length: 1.0.5 @@ -7821,6 +8641,16 @@ snapshots: undici-types@6.21.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + unique-filename@4.0.0: dependencies: unique-slug: 5.0.0 @@ -7829,6 +8659,29 @@ snapshots: dependencies: imurmurhash: 0.1.4 + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@0.1.2: {} universalify@2.0.1: {} @@ -7875,6 +8728,16 @@ snapshots: extsprintf: 1.4.1 optional: true + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite@7.3.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2): dependencies: esbuild: 0.27.2 @@ -8005,3 +8868,5 @@ snapshots: optionalDependencies: '@types/react': 19.2.8 react: 19.2.3 + + zwitch@2.0.4: {} diff --git a/src/main/agent/runtime.ts b/src/main/agent/runtime.ts index 0eab71c..f9a7482 100644 --- a/src/main/agent/runtime.ts +++ b/src/main/agent/runtime.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { createDeepAgent } from 'deepagents' import { app } from 'electron' import { join } from 'path' @@ -6,6 +7,11 @@ import { ChatAnthropic } from '@langchain/anthropic' import { ChatOpenAI } from '@langchain/openai' import { SqlJsSaver } from '../checkpointer/sqljs-saver' +import type * as _lcTypes from 'langchain' +import type * as _lcMessages from '@langchain/core/messages' +import type * as _lcLanggraph from '@langchain/langgraph' +import type * as _lcZodTypes from '@langchain/core/utils/types' + // Singleton checkpointer instance let checkpointer: SqlJsSaver | null = null @@ -19,7 +25,7 @@ export async function getCheckpointer(): Promise { } // Get the appropriate model instance based on configuration -function getModelInstance(modelId?: string) { +function getModelInstance(modelId?: string): ChatAnthropic | ChatOpenAI | string { const model = modelId || getDefaultModel() console.log('[Runtime] Using model:', model) @@ -54,30 +60,29 @@ function getModelInstance(modelId?: string) { } // Create agent runtime with configured model and checkpointer +export type AgentRuntime = ReturnType + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export async function createAgentRuntime(modelId?: string) { console.log('[Runtime] Creating agent runtime...') - + const model = getModelInstance(modelId) console.log('[Runtime] Model instance created:', typeof model) - + const saver = await getCheckpointer() console.log('[Runtime] Checkpointer ready') - // Using type assertion to work around version compatibility issues - // between @langchain packages and deepagentsjs types - // eslint-disable-next-line @typescript-eslint/no-explicit-any const agent = createDeepAgent({ - model: model as any, - checkpointer: saver as any + model: model, + checkpointer: saver }) - - console.log('[Runtime] Deep agent created') + console.log('[Runtime] Deep agent created') return agent } // Clean up resources -export async function closeRuntime() { +export async function closeRuntime(): Promise { if (checkpointer) { await checkpointer.close() checkpointer = null diff --git a/src/main/db/index.ts b/src/main/db/index.ts index 237d36a..f584b89 100644 --- a/src/main/db/index.ts +++ b/src/main/db/index.ts @@ -4,7 +4,7 @@ import { dirname, join } from 'path' import { app } from 'electron' // Database path in user data directory -const getDbPath = () => join(app.getPath('userData'), 'openwork.sqlite') +const getDbPath = (): string => join(app.getPath('userData'), 'openwork.sqlite') let db: SqlJsDatabase | null = null let saveTimer: ReturnType | null = null @@ -176,10 +176,7 @@ export function getThread(threadId: string): Thread | null { return thread } -export function createThread( - threadId: string, - metadata?: Record -): Thread { +export function createThread(threadId: string, metadata?: Record): Thread { const database = getDb() const now = Date.now() @@ -217,7 +214,9 @@ export function updateThread( if (updates.metadata !== undefined) { setClauses.push('metadata = ?') - values.push(typeof updates.metadata === 'string' ? updates.metadata : JSON.stringify(updates.metadata)) + values.push( + typeof updates.metadata === 'string' ? updates.metadata : JSON.stringify(updates.metadata) + ) } if (updates.status !== undefined) { setClauses.push('status = ?') @@ -234,10 +233,7 @@ export function updateThread( values.push(threadId) - database.run( - `UPDATE threads SET ${setClauses.join(', ')} WHERE thread_id = ?`, - values - ) + database.run(`UPDATE threads SET ${setClauses.join(', ')} WHERE thread_id = ?`, values) saveToDisk() diff --git a/src/main/ipc/agent.ts b/src/main/ipc/agent.ts index db12b26..0c98b21 100644 --- a/src/main/ipc/agent.ts +++ b/src/main/ipc/agent.ts @@ -1,239 +1,215 @@ import { IpcMain, BrowserWindow } from 'electron' -import { HumanMessage } from '@langchain/core/messages' +import { HumanMessage, BaseMessage, AIMessage, AIMessageChunk } from '@langchain/core/messages' import { createAgentRuntime } from '../agent/runtime' -import type { HITLDecision, StreamEvent } from '../types' +import type { HITLDecision } from '../types' // Track active runs for cancellation const activeRuns = new Map() -export function registerAgentHandlers(ipcMain: IpcMain) { +/** + * Serialize a LangChain message to a plain object for IPC + */ +function serializeMessage(msg: BaseMessage): { + id: string + type: 'human' | 'ai' | 'tool' | 'system' + content: string + tool_calls?: Array<{ id: string; name: string; args: Record }> +} { + // Extract content as string + let content = '' + if (typeof msg.content === 'string') { + content = msg.content + } else if (Array.isArray(msg.content)) { + content = msg.content + .filter((block): block is { type: 'text'; text: string } => block.type === 'text') + .map((block) => block.text) + .join('') + } + + // Map LangChain message types + const type = + msg.type === 'human' + ? 'human' + : msg.type === 'ai' + ? 'ai' + : msg.type === 'tool' + ? 'tool' + : 'system' + + // Extract tool calls from AI messages + const toolCalls = + msg instanceof AIMessage && msg.tool_calls?.length + ? msg.tool_calls.map((tc) => ({ + id: tc.id || crypto.randomUUID(), + name: tc.name, + args: tc.args as Record + })) + : undefined + + return { + id: msg.id || crypto.randomUUID(), + type, + content, + tool_calls: toolCalls + } +} + +export function registerAgentHandlers(ipcMain: IpcMain): void { console.log('[Agent] Registering agent handlers...') - + // Handle agent invocation with streaming - ipcMain.on('agent:invoke', async (event, { threadId, message }: { threadId: string; message: string }) => { - const channel = `agent:stream:${threadId}` - const window = BrowserWindow.fromWebContents(event.sender) - - console.log('[Agent] Received invoke request:', { threadId, message: message.substring(0, 50) }) - - if (!window) { - console.error('[Agent] No window found') - return - } + ipcMain.on( + 'agent:invoke', + async (event, { threadId, message }: { threadId: string; message: string }) => { + const channel = `agent:stream:${threadId}` + const window = BrowserWindow.fromWebContents(event.sender) - const abortController = new AbortController() - activeRuns.set(threadId, abortController) + console.log('[Agent] Received invoke request:', { + threadId, + message: message.substring(0, 50) + }) - try { - console.log('[Agent] Creating runtime...') - const agent = await createAgentRuntime() - console.log('[Agent] Runtime created, starting stream...') - - // Create proper HumanMessage - const humanMessage = new HumanMessage(message) - - // Track seen message IDs to avoid duplicates - const seenMessageIds = new Set() - - // Stream with values mode to get full state after each step - // Note: 'messages' mode was causing tool call corruption, so we stick with 'values' - const stream = await agent.stream( - { messages: [humanMessage] }, - { - configurable: { thread_id: threadId }, - signal: abortController.signal, - streamMode: 'values', - recursionLimit: 1000 // Match Python deepagents behavior - } - ) - console.log('[Agent] Stream started with streamMode: values') + if (!window) { + console.error('[Agent] No window found') + return + } - for await (const chunk of stream) { - if (abortController.signal.aborted) break - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const state = chunk as any - console.log('[Agent] Chunk keys:', Object.keys(state || {})) - - // Process messages from state - if (state.messages && Array.isArray(state.messages)) { - for (const msg of state.messages) { - const msgId = msg.id || crypto.randomUUID() - - // Skip if we've already sent this message - if (seenMessageIds.has(msgId)) continue - - // Determine the role from the message type - let role: 'user' | 'assistant' | 'system' | 'tool' = 'assistant' - if (typeof msg._getType === 'function') { - const msgType = msg._getType() - if (msgType === 'human') role = 'user' - else if (msgType === 'ai') role = 'assistant' - else if (msgType === 'system') role = 'system' - else if (msgType === 'tool') role = 'tool' - } - - // Extract content - let content: string = '' - if (typeof msg.content === 'string') { - content = msg.content - } else if (Array.isArray(msg.content)) { - content = msg.content - .filter((block: { type?: string }) => block.type === 'text') - .map((block: { text?: string }) => block.text || '') - .join('') - } - - // Only send assistant messages with content - if (role === 'assistant' && content) { - seenMessageIds.add(msgId) - - const streamEvent: StreamEvent = { - type: 'message', - message: { - id: msgId, - role, - content, - tool_calls: msg.tool_calls, - created_at: new Date() - } + const abortController = new AbortController() + activeRuns.set(threadId, abortController) + + try { + const agent = await createAgentRuntime() + const humanMessage = new HumanMessage(message) + + // Track state for deduplication + const seenMessageIds = new Set() + let currentMessageId: string | null = null + + // Stream with both modes: + // - 'messages' for real-time token streaming + // - 'values' for full state (todos, files, etc.) + const stream = await agent.stream( + { messages: [humanMessage] }, + { + configurable: { thread_id: threadId }, + signal: abortController.signal, + streamMode: ['messages', 'values'], + recursionLimit: 1000 + } + ) + + for await (const chunk of stream) { + if (abortController.signal.aborted) break + + // With multiple stream modes, chunks are tuples: [mode, data] + const [mode, data] = chunk as [string, unknown] + + if (mode === 'messages') { + // Messages mode returns [message, metadata] tuples + const [msgChunk, metadata] = data as [AIMessageChunk, { langgraph_node?: string }] + console.log('[Agent] Message chunk:', { + type: msgChunk?.constructor?.name, + node: metadata?.langgraph_node + }) + + // Process AI message chunks (from any node that produces AI messages) + if (msgChunk instanceof AIMessageChunk) { + const content = + typeof msgChunk.content === 'string' + ? msgChunk.content + : Array.isArray(msgChunk.content) + ? msgChunk.content + .filter((b): b is { type: 'text'; text: string } => b.type === 'text') + .map((b) => b.text) + .join('') + : '' + + if (content) { + // Track message ID for grouping tokens + const msgId = msgChunk.id || currentMessageId || crypto.randomUUID() + currentMessageId = msgId + + console.log('[Agent] Sending token:', content.substring(0, 50)) + window.webContents.send(channel, { + type: 'token', + messageId: msgId, + token: content + }) } - window.webContents.send(channel, streamEvent) - console.log('[Agent] Sent message:', msgId.substring(0, 20)) - } - } - } - - // Check for todos in agent state - if (state.todos && Array.isArray(state.todos)) { - const todosEvent: StreamEvent = { - type: 'todos', - todos: (state.todos as Array<{ id?: string; content?: string; status?: string }>).map((t) => ({ - id: t.id || crypto.randomUUID(), - content: t.content || '', - status: (t.status || 'pending') as 'pending' | 'in_progress' | 'completed' | 'cancelled' - })) - } - window.webContents.send(channel, todosEvent) - } - // Check for workspace/file state - // deepagents stores files as Record (object keyed by path) - const filesObj = state.files as Record | undefined - const workspacePath = (state.workspacePath as string) || process.cwd() - - if (filesObj && typeof filesObj === 'object' && !Array.isArray(filesObj)) { - // Convert object format to array format - const files = Object.entries(filesObj).map(([filePath, data]) => ({ - path: filePath, - is_dir: false, - size: typeof data?.content === 'string' ? data.content.length : undefined - })) - - if (files.length > 0) { - console.log('[Agent] Sending workspace event with', files.length, 'files') - const workspaceEvent: StreamEvent = { - type: 'workspace', - files, - path: workspacePath + // Handle tool calls in the chunk + if (msgChunk.tool_call_chunks?.length) { + window.webContents.send(channel, { + type: 'tool_call', + messageId: currentMessageId, + tool_calls: msgChunk.tool_call_chunks + }) + } } - window.webContents.send(channel, workspaceEvent) - } - } else if (Array.isArray(filesObj)) { - // Handle legacy array format if present - const files = (filesObj as Array<{ path: string; is_dir?: boolean; size?: number }>) - if (files.length > 0) { - const workspaceEvent: StreamEvent = { - type: 'workspace', - files: files.map((f) => ({ - path: f.path, - is_dir: f.is_dir, - size: f.size - })), - path: workspacePath + } else if (mode === 'values') { + // Values mode returns the full state + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const state = data as any + + // Send complete messages (for final state) + const messages = (state.messages as BaseMessage[] | undefined) + ?.filter((msg) => { + const id = msg.id || '' + if (seenMessageIds.has(id)) return false + if (msg.type === 'ai' && msg.content) { + seenMessageIds.add(id) + return true + } + return false + }) + .map(serializeMessage) + + // Reset current message ID when we get a complete message + if (messages?.length) { + currentMessageId = null } - window.webContents.send(channel, workspaceEvent) + + window.webContents.send(channel, { + type: 'values', + data: { + messages, + todos: state.todos, + files: state.files, + workspacePath: state.workspacePath, + subagents: state.subagents, + interrupt: state.__interrupt__ + } + }) } } - // Check for subagents in agent state - const subagentsRaw = state.subagents as Array<{ - id?: string - name?: string - type?: string - description?: string - status?: string - startedAt?: Date | string - completedAt?: Date | string - }> | undefined - - if (subagentsRaw && Array.isArray(subagentsRaw) && subagentsRaw.length > 0) { - console.log('[Agent] Sending subagents event with', subagentsRaw.length, 'subagents') - const subagentsEvent: StreamEvent = { - type: 'subagents', - subagents: subagentsRaw.map((s) => ({ - id: s.id || crypto.randomUUID(), - name: s.name || s.type || 'Subagent', - description: s.description || '', - status: (s.status || 'pending') as 'pending' | 'running' | 'completed' | 'failed', - startedAt: s.startedAt ? new Date(s.startedAt) : undefined, - completedAt: s.completedAt ? new Date(s.completedAt) : undefined - })) - } - window.webContents.send(channel, subagentsEvent) - } - - // Check for interrupts (HITL) - const interrupt = state.__interrupt__ as { id?: string; tool_call?: unknown } | undefined - if (interrupt) { - const streamEvent: StreamEvent = { - type: 'interrupt', - request: { - id: interrupt.id || crypto.randomUUID(), - tool_call: interrupt.tool_call as { id: string; name: string; args: Record }, - allowed_decisions: ['approve', 'reject', 'edit'] - } - } - window.webContents.send(channel, streamEvent) - } + // Send done event + window.webContents.send(channel, { type: 'done' }) + } catch (error) { + console.error('[Agent] Error:', error) + window.webContents.send(channel, { + type: 'error', + error: error instanceof Error ? error.message : 'Unknown error' + }) + } finally { + activeRuns.delete(threadId) } - - // Send done event - console.log('[Agent] Stream complete, sending done event') - const doneEvent: StreamEvent = { type: 'done', result: null } - window.webContents.send(channel, doneEvent) - - } catch (error) { - console.error('[Agent] Error:', error) - const errorEvent: StreamEvent = { - type: 'error', - error: error instanceof Error ? error.message : 'Unknown error' - } - window.webContents.send(channel, errorEvent) - } finally { - activeRuns.delete(threadId) } - }) + ) // Handle HITL interrupt response - ipcMain.handle('agent:interrupt', async (_event, { threadId, decision }: { threadId: string; decision: HITLDecision }) => { - const agent = await createAgentRuntime() - - // Get the current state - const config = { configurable: { thread_id: threadId } } - - // Resume with the decision - if (decision.type === 'approve') { - // Continue execution - await agent.invoke(null, config) - } else if (decision.type === 'reject') { - // Cancel the tool call - // The agent will handle this via Command - } else if (decision.type === 'edit') { - // Update the tool call args and continue - // This requires updating state before resuming + ipcMain.handle( + 'agent:interrupt', + async (_event, { threadId, decision }: { threadId: string; decision: HITLDecision }) => { + const agent = await createAgentRuntime() + const config = { configurable: { thread_id: threadId } } + + if (decision.type === 'approve') { + await agent.invoke(null, config) + } + // reject and edit handled by Command in future } - }) + ) // Handle cancellation ipcMain.handle('agent:cancel', async (_event, { threadId }: { threadId: string }) => { diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 9848a28..4284106 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -16,6 +16,12 @@ interface ElectronAPI { interface CustomAPI { agent: { invoke: (threadId: string, message: string, onEvent: (event: StreamEvent) => void) => () => void + streamAgent: ( + threadId: string, + message: string, + command: unknown, + onEvent: (event: StreamEvent) => void + ) => () => void interrupt: (threadId: string, decision: HITLDecision) => Promise cancel: (threadId: string) => Promise } diff --git a/src/preload/index.ts b/src/preload/index.ts index 843df86..06f88a9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -25,28 +25,65 @@ const api = { agent: { // Send message and receive events via callback invoke: ( - threadId: string, - message: string, + threadId: string, + message: string, onEvent: (event: StreamEvent) => void ): (() => void) => { console.log('[Preload] invoke() called', { threadId, message: message.substring(0, 50) }) - + const channel = `agent:stream:${threadId}` - - const handler = (_: unknown, data: StreamEvent) => { + + const handler = (_: unknown, data: StreamEvent): void => { console.log('[Preload] Received event:', data.type) onEvent(data) - + // Clean up listener on terminal events if (data.type === 'done' || data.type === 'error') { ipcRenderer.removeListener(channel, handler) } } - + ipcRenderer.on(channel, handler) console.log('[Preload] Sending agent:invoke IPC') ipcRenderer.send('agent:invoke', { threadId, message }) - + + // Return cleanup function + return () => { + ipcRenderer.removeListener(channel, handler) + } + }, + // Stream agent events for useStream transport + streamAgent: ( + threadId: string, + message: string, + command: unknown, + onEvent: (event: StreamEvent) => void + ): (() => void) => { + console.log('[Preload] streamAgent() called', { threadId, message: message.substring(0, 50) }) + + const channel = `agent:stream:${threadId}` + + const handler = (_: unknown, data: StreamEvent): void => { + console.log('[Preload] Received stream event:', data.type) + onEvent(data) + + // Clean up listener on terminal events + if (data.type === 'done' || data.type === 'error') { + ipcRenderer.removeListener(channel, handler) + } + } + + ipcRenderer.on(channel, handler) + + // If we have a command, it might be a resume/retry + if (command) { + console.log('[Preload] Sending agent:resume IPC') + ipcRenderer.send('agent:resume', { threadId, command }) + } else { + console.log('[Preload] Sending agent:invoke IPC') + ipcRenderer.send('agent:invoke', { threadId, message }) + } + // Return cleanup function return () => { ipcRenderer.removeListener(channel, handler) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index cf1c036..c6d15f9 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -4,18 +4,21 @@ import { ChatContainer } from '@/components/chat/ChatContainer' import { RightPanel } from '@/components/panels/RightPanel' import { useAppStore } from '@/lib/store' -function App() { +function App(): React.JSX.Element { const { currentThreadId, loadThreads, createThread, setSettingsOpen } = useAppStore() const [isLoading, setIsLoading] = useState(true) // Keyboard shortcuts - const handleKeyDown = useCallback((e: KeyboardEvent) => { - // Cmd+, for settings - if ((e.metaKey || e.ctrlKey) && e.key === ',') { - e.preventDefault() - setSettingsOpen(true) - } - }, [setSettingsOpen]) + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + // Cmd+, for settings + if ((e.metaKey || e.ctrlKey) && e.key === ',') { + e.preventDefault() + setSettingsOpen(true) + } + }, + [setSettingsOpen] + ) useEffect(() => { window.addEventListener('keydown', handleKeyDown) @@ -23,7 +26,7 @@ function App() { }, [handleKeyDown]) useEffect(() => { - async function init() { + async function init(): Promise { try { await loadThreads() // Create a default thread if none exist @@ -52,7 +55,7 @@ function App() {
{/* Draggable titlebar region */}
- + {/* Main content area */}
{/* Left Sidebar - Thread List */} diff --git a/src/renderer/src/components/chat/ChatContainer.tsx b/src/renderer/src/components/chat/ChatContainer.tsx index 6379404..a4859c0 100644 --- a/src/renderer/src/components/chat/ChatContainer.tsx +++ b/src/renderer/src/components/chat/ChatContainer.tsx @@ -1,54 +1,251 @@ -import { useState, useRef, useEffect } from 'react' +import { useState, useRef, useEffect, useMemo, useCallback } from 'react' import { Send, Square, Loader2 } from 'lucide-react' +import { useStream } from '@langchain/langgraph-sdk/react' import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' import { useAppStore } from '@/lib/store' import { MessageBubble } from './MessageBubble' import { ApprovalDialog } from '@/components/hitl/ApprovalDialog' +import { ElectronIPCTransport } from '@/lib/electron-transport' +import type { Message } from '@/types' interface ChatContainerProps { threadId: string } -export function ChatContainer({ threadId }: ChatContainerProps) { +// Define custom event data types +interface TodoEventData { + id?: string + content?: string + status?: string +} + +interface FileEventData { + path: string + is_dir?: boolean + size?: number +} + +interface SubagentEventData { + id?: string + name?: string + description?: string + status?: string + startedAt?: Date + completedAt?: Date +} + +interface MessageEventData { + id?: string + type?: string + role?: string + content?: string + tool_calls?: unknown[] + created_at?: Date +} + +interface CustomEventData { + type?: string + message?: MessageEventData + todos?: TodoEventData[] + files?: FileEventData[] + path?: string + subagents?: SubagentEventData[] + request?: unknown +} + +export function ChatContainer({ threadId }: ChatContainerProps): React.JSX.Element { const [input, setInput] = useState('') const inputRef = useRef(null) const scrollRef = useRef(null) - - const { - messages, - isThreadStreaming, - getStreamingContent, + + const { + messages: storeMessages, pendingApproval, - sendMessage + setTodos, + setWorkspaceFiles, + setWorkspacePath, + setSubagents, + setPendingApproval, + appendMessage, + loadThreads, + generateTitleForFirstMessage } = useAppStore() - - // Get streaming state for this specific thread - const isStreaming = isThreadStreaming(threadId) - const streamingContent = getStreamingContent(threadId) + + // Create transport instance (memoized to avoid recreating) + const transport = useMemo(() => new ElectronIPCTransport(), []) + + // Handle custom events from the stream + const handleCustomEvent = useCallback( + (data: CustomEventData): void => { + console.log('[ChatContainer] Custom event:', data) + switch (data.type) { + case 'message': + if (data.message) { + const msg = data.message + const storeMsg: Message = { + id: msg.id || crypto.randomUUID(), + role: + msg.role === 'user' || msg.type === 'human' + ? 'user' + : msg.role === 'assistant' || msg.type === 'ai' + ? 'assistant' + : msg.role === 'tool' || msg.type === 'tool' + ? 'tool' + : 'system', + content: msg.content || '', + tool_calls: msg.tool_calls as Message['tool_calls'], + created_at: msg.created_at ? new Date(msg.created_at) : new Date() + } + console.log('[ChatContainer] Adding message:', storeMsg) + appendMessage(storeMsg) + } + break + case 'todos': + if (Array.isArray(data.todos)) { + setTodos( + data.todos.map((t) => ({ + id: t.id || crypto.randomUUID(), + content: t.content || '', + status: (t.status || 'pending') as + | 'pending' + | 'in_progress' + | 'completed' + | 'cancelled' + })) + ) + } + break + case 'workspace': + if (Array.isArray(data.files)) { + setWorkspaceFiles( + data.files.map((f) => ({ + path: f.path, + is_dir: f.is_dir, + size: f.size + })) + ) + } + if (data.path) { + setWorkspacePath(data.path) + } + break + case 'subagents': + if (Array.isArray(data.subagents)) { + setSubagents( + data.subagents.map((s) => ({ + id: s.id || crypto.randomUUID(), + name: s.name || 'Subagent', + description: s.description || '', + status: (s.status || 'pending') as 'pending' | 'running' | 'completed' | 'failed', + startedAt: s.startedAt, + completedAt: s.completedAt + })) + ) + } + break + case 'interrupt': + if (data.request) { + setPendingApproval(data.request as Parameters[0]) + } + break + } + }, + [setTodos, setWorkspaceFiles, setWorkspacePath, setSubagents, setPendingApproval, appendMessage] + ) + + // Use the useStream hook with our custom transport + const stream = useStream({ + transport, + threadId, + messagesKey: 'messages', + onCustomEvent: (data): void => { + handleCustomEvent(data as CustomEventData) + }, + onError: (error): void => { + console.error('[ChatContainer] Stream error:', error) + } + }) + console.log('[ChatContainer] Stream:', stream.messages) + + // Refresh threads when loading state changes from true to false (stream completed) + const prevLoadingRef = useRef(false) + useEffect(() => { + if (prevLoadingRef.current && !stream.isLoading) { + // Stream just completed + loadThreads() + } + prevLoadingRef.current = stream.isLoading + }, [stream.isLoading, loadThreads]) + + // Combine store messages with streaming messages + const displayMessages = useMemo(() => { + // Get IDs of messages already in the store + const storeMessageIds = new Set(storeMessages.map((m) => m.id)) + + // Get streaming messages that aren't in the store yet + const streamingMsgs: Message[] = (stream.messages || []) + .filter((m): m is typeof m & { id: string } => !!m.id && !storeMessageIds.has(m.id)) + .map((m) => ({ + id: m.id, + role: (m.type === 'human' ? 'user' : 'assistant') as Message['role'], + content: typeof m.content === 'string' ? m.content : '', + created_at: new Date() + })) + + return [...storeMessages, ...streamingMsgs] + }, [storeMessages, stream.messages]) // Auto-scroll on new messages useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight } - }, [messages, streamingContent, threadId]) + }, [displayMessages, stream.isLoading, threadId]) // Focus input on mount useEffect(() => { inputRef.current?.focus() }, [threadId]) - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent): Promise => { e.preventDefault() - if (!input.trim() || isStreaming) return + if (!input.trim() || stream.isLoading) return const message = input.trim() setInput('') - await sendMessage(message) + + // Check if this is the first message (for title generation) + const isFirstMessage = storeMessages.length === 0 + + // Add user message to store immediately + const userMessage: Message = { + id: crypto.randomUUID(), + role: 'user', + content: message, + created_at: new Date() + } + appendMessage(userMessage) + + // Generate title for first message + if (isFirstMessage) { + generateTitleForFirstMessage(threadId, message) + } + + // Submit via useStream + await stream.submit( + { + messages: [{ type: 'human', content: message }] + }, + { + config: { + configurable: { thread_id: threadId } + } + } + ) } - const handleKeyDown = (e: React.KeyboardEvent) => { + const handleKeyDown = (e: React.KeyboardEvent): void => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSubmit(e) @@ -56,7 +253,7 @@ export function ChatContainer({ threadId }: ChatContainerProps) { } // Auto-resize textarea based on content - const adjustTextareaHeight = () => { + const adjustTextareaHeight = (): void => { const textarea = inputRef.current if (textarea) { textarea.style.height = 'auto' @@ -68,8 +265,8 @@ export function ChatContainer({ threadId }: ChatContainerProps) { adjustTextareaHeight() }, [input]) - const handleCancel = async () => { - await window.api.agent.cancel(threadId) + const handleCancel = async (): Promise => { + await stream.stop() } return ( @@ -78,36 +275,24 @@ export function ChatContainer({ threadId }: ChatContainerProps) {
- {messages.length === 0 && !isStreaming && ( -
-
NEW THREAD
-
Start a conversation with the agent
-
- )} + {displayMessages.length === 0 && !stream.isLoading && ( +
+
NEW THREAD
+
Start a conversation with the agent
+
+ )} - {messages.map((message) => ( - - ))} + {displayMessages.map((message) => ( + + ))} - {/* Streaming indicator */} - {isStreaming && streamingContent && ( - - )} - - {isStreaming && !streamingContent && ( -
- - Agent is thinking... -
- )} + {/* Streaming indicator - only show if no streaming messages yet */} + {stream.isLoading && stream.messages.length === 0 && ( +
+ + Agent is thinking... +
+ )}
@@ -122,28 +307,18 @@ export function ChatContainer({ threadId }: ChatContainerProps) { onChange={(e) => setInput(e.target.value)} onKeyDown={handleKeyDown} placeholder="Message..." - disabled={isStreaming} + disabled={stream.isLoading} className="flex-1 min-w-0 resize-none rounded-sm border border-border bg-background px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50" rows={1} style={{ minHeight: '48px', maxHeight: '200px' }} />
- {isStreaming ? ( - ) : ( - )} diff --git a/src/renderer/src/components/sidebar/ThreadSidebar.tsx b/src/renderer/src/components/sidebar/ThreadSidebar.tsx index d0293f8..b72d054 100644 --- a/src/renderer/src/components/sidebar/ThreadSidebar.tsx +++ b/src/renderer/src/components/sidebar/ThreadSidebar.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Plus, MessageSquare, Trash2, Settings, Pencil, Loader2 } from 'lucide-react' +import { Plus, MessageSquare, Trash2, Settings, Pencil } from 'lucide-react' import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' import { Separator } from '@/components/ui/separator' @@ -12,47 +12,46 @@ import { ContextMenuContent, ContextMenuItem, ContextMenuSeparator, - ContextMenuTrigger, + ContextMenuTrigger } from '@/components/ui/context-menu' // Get version from package.json (injected at build time or via preload) const APP_VERSION = '0.1.0' -export function ThreadSidebar() { - const { - threads, - currentThreadId, - createThread, - selectThread, +export function ThreadSidebar(): React.JSX.Element { + const { + threads, + currentThreadId, + createThread, + selectThread, deleteThread, updateThread, - settingsOpen, - setSettingsOpen, - streamingThreads + settingsOpen, + setSettingsOpen } = useAppStore() - + const [editingThreadId, setEditingThreadId] = useState(null) const [editingTitle, setEditingTitle] = useState('') - - const startEditing = (threadId: string, currentTitle: string) => { + + const startEditing = (threadId: string, currentTitle: string): void => { setEditingThreadId(threadId) setEditingTitle(currentTitle || '') } - - const saveTitle = async () => { + + const saveTitle = async (): Promise => { if (editingThreadId && editingTitle.trim()) { await updateThread(editingThreadId, { title: editingTitle.trim() }) } setEditingThreadId(null) setEditingTitle('') } - - const cancelEditing = () => { + + const cancelEditing = (): void => { setEditingThreadId(null) setEditingTitle('') } - const handleNewThread = async () => { + const handleNewThread = async (): Promise => { await createThread({ title: `Thread ${new Date().toLocaleDateString()}` }) } @@ -62,9 +61,7 @@ export function ThreadSidebar() {
OPENWORK - - {APP_VERSION} - + {APP_VERSION}