From 02da571482834b544ddae1a54a218e62b56bbadf Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 5 Oct 2026 06:56:09 -0400 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20chore:=20Bump=20LibreChat?= =?UTF-8?q?=20Packages=20to=20Latest=20&=20Support=20New=20Schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update @librechat/data-schemas (0.0.56 → 0.0.74) and librechat-data-provider (0.8.509 → 0.8.524), and adapt the panel: - Audit log: accept and render the new `permission.insights_assigned` / `permission.insights_removed` actions (agent target, "View insights" subject, grantee row). Previously any such entry failed response validation and broke the whole page. - Roles: add the new `SCHEDULES` permission type (USE, CREATE). - Interface permissions: mirror upstream `RUNTIME_CONFIG_INTERFACE_FIELDS` so the bare `interface.schedules` runtime toggle is saved instead of being silently dropped, while its `use`/`create` sub-keys stay blocked. - Langfuse: drop the forward-compat schema shim now that the pinned data-provider ships the `langfuse` group. - Config: add section meta for `filters`, `permissions`, and `openapi`, plus locale keys for ~190 new schema fields and the new `read:insights` / `manage:code_environments` capabilities. - Tests: add sample overrides for new fields with stricter validation. --- bun.lock | 150 +++++++++++- package.json | 4 +- .../access/RolePermissionsPanel.tsx | 1 + src/components/configuration/configMeta.ts | 15 ++ .../grants/AuditLogDetailDrawer.tsx | 42 ++-- src/components/grants/AuditLogTab.tsx | 24 +- src/components/grants/auditLogUtils.test.ts | 61 +++++ src/components/grants/auditLogUtils.ts | 56 +++++ src/components/grants/index.ts | 1 + src/constants/audit.ts | 2 + src/constants/role.ts | 1 + src/locales/en/translation.json | 226 ++++++++++++++++++ src/server/config.test.ts | 36 +-- src/server/config.ts | 42 +--- src/types/grant.ts | 21 +- src/utils/interfacePermissions.test.ts | 45 ++++ src/utils/interfacePermissions.ts | 25 +- 17 files changed, 657 insertions(+), 95 deletions(-) create mode 100644 src/utils/interfacePermissions.test.ts diff --git a/bun.lock b/bun.lock index ec864745..b1922ff7 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "tanstack-start-app", "dependencies": { "@clickhouse/click-ui": "0.9.1", - "@librechat/data-schemas": "^0.0.56", + "@librechat/data-schemas": "^0.0.74", "@radix-ui/react-dialog": "1.1.15", "@tailwindcss/vite": "^4.3.1", "@tanstack/react-devtools": "0.10.0", @@ -26,7 +26,7 @@ "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.4.2", "js-yaml": "^4.2.0", - "librechat-data-provider": "^0.8.509", + "librechat-data-provider": "^0.8.524", "lucide-react": "^0.545.0", "prom-client": "^15.1.3", "react": "^19.2.7", @@ -292,7 +292,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@librechat/data-schemas": ["@librechat/data-schemas@0.0.56", "", { "peerDependencies": { "jsonwebtoken": "^9.0.2", "klona": "^2.0.6", "librechat-data-provider": "*", "lodash": "^4.17.23", "meilisearch": "^0.38.0", "mongoose": "^8.23.1", "nanoid": "^3.3.7", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" } }, "sha512-Wv8/LSN7BVecP5j+/7+N/gP/DlimCaDxm7K1CylUv9ExF0PPCzTQ1ust8+Oe0TGuGtmSfAX1VUiL51bHJ8AarA=="], + "@librechat/data-schemas": ["@librechat/data-schemas@0.0.74", "", { "dependencies": { "mdast-util-directive": "^3.0.0", "mdast-util-from-markdown": "^2.0.1", "mdast-util-gfm": "^3.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-directive": "^3.0.1", "micromark-extension-gfm": "^3.0.0", "micromark-extension-llm-math": "^3.1.0", "micromark-util-decode-string": "^2.0.0" }, "peerDependencies": { "jsonwebtoken": "^9.0.2", "klona": "^2.0.6", "librechat-data-provider": "*", "lodash": "^4.17.23", "meilisearch": "^0.38.0", "mongoose": "^8.24.1", "nanoid": "^3.3.18", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" } }, "sha512-KYcZyY2MuzMhYPiRfzJWPscuRmL3zKBYecZ80A+hdaaWVGP6JyNHUJc29XGLthG6w4AXrEGcO3h36TQdz+Nu3Q=="], "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.6", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g=="], @@ -620,6 +620,8 @@ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "4.0.2", "assertion-error": "2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], @@ -632,6 +634,12 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/katex": ["@types/katex@0.16.8", "", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@22.20.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g=="], "@types/prismjs": ["@types/prismjs@1.26.6", "", {}, "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw=="], @@ -762,7 +770,7 @@ "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], - "axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], + "axios": ["axios@1.20.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg=="], "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "7.29.0", "@babel/parser": "7.29.0", "@babel/traverse": "7.29.0", "@babel/types": "7.29.0" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], @@ -802,12 +810,16 @@ "caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], @@ -856,6 +868,8 @@ "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], + "croner": ["croner@10.0.1", "", {}, "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g=="], + "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -900,6 +914,8 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], @@ -1016,7 +1032,7 @@ "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], @@ -1066,7 +1082,7 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], @@ -1172,6 +1188,8 @@ "kareem": ["kareem@2.6.3", "", {}, "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q=="], + "katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "klaw-sync": ["klaw-sync@6.0.0", "", { "dependencies": { "graceful-fs": "^4.1.11" } }, "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ=="], @@ -1184,7 +1202,7 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - "librechat-data-provider": ["librechat-data-provider@0.8.509", "", { "dependencies": { "axios": "^1.16.0", "dayjs": "^1.11.13", "js-yaml": "^4.2.0", "zod": "^3.22.4" }, "peerDependencies": { "@tanstack/react-query": "^4.28.0" } }, "sha512-5GEkiKbPSYBTodiSPiu98skFjcZ1TiiJZuNZtc9mSBLCTi2GpCSM69+tAKENH5mex29/JCAd7HjtXrvVP/ZtaA=="], + "librechat-data-provider": ["librechat-data-provider@0.8.524", "", { "dependencies": { "axios": "^1.20.0", "croner": "^10.0.1", "dayjs": "^1.11.13", "js-yaml": "^4.3.2", "re2js": "^2.8.6", "zod": "^3.22.4" }, "peerDependencies": { "@tanstack/react-query": "^4.28.0" } }, "sha512-AqRhbSVdVT9cn1Kt9i5qrUD19amcvqMNDbOAsoZdyvkTwESgwn6tz8BKFrC1G/AivSHC4EpcWTlw0CIuJXhMUA=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -1240,6 +1258,8 @@ "logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], @@ -1250,8 +1270,36 @@ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + "mdast-util-directive": ["mdast-util-directive@3.1.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-character": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Gqpa3MHorXYEZ0VfbroyvBlVKUfz371V6mIERqOYMcb0W1D3LwmFq1ZW4O2TDrbn+4ZmGLwMWeFRJgYZ+bc5fQ=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-math": ["mdast-util-math@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.1.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.2.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-character": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], "meilisearch": ["meilisearch@0.38.0", "", { "dependencies": { "cross-fetch": "^3.1.6" } }, "sha512-bHaq8nYxSKw9/Qslq1Zes5g9tHgFkxy/I9o8942wv2PqlNOT0CzptIkh/x98N52GikoSZOXSQkgt6oMjtf5uZw=="], @@ -1260,6 +1308,68 @@ "memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="], + "micromark": ["micromark@4.0.3", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-edit-map": "^1.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.4", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.1.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-edit-map": "^1.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.3" } }, "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w=="], + + "micromark-extension-directive": ["micromark-extension-directive@3.0.2", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.2", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-extension-llm-math": ["micromark-extension-llm-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VIYHuIEk0gpHrojEtNGaxGwdpSLtdWYlLL2vu9PM4M1ilEtak10S8F9zzbNAPBNRoWFs/bjs+J7R3yUBoIQUEA=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-edit-map": ["micromark-util-edit-map@1.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.3", "", {}, "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -1374,6 +1484,8 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "re2js": ["re2js@2.8.6", "", {}, "sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg=="], + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], @@ -1496,6 +1608,8 @@ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], @@ -1566,6 +1680,16 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "acorn": "8.16.0", "picomatch": "4.0.3", "webpack-virtual-modules": "0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], @@ -1644,6 +1768,8 @@ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "3.1.1" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -1948,24 +2074,34 @@ "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "es-set-tostringtag/hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "eslint-plugin-click-ui/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], "eslint-plugin-import-x/@typescript-eslint/types": ["@typescript-eslint/types@8.57.1", "", {}, "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ=="], + "get-intrinsic/hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "i18next/@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "librechat-data-provider/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + "librechat-data-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], "log-update/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "mongodb-connection-string-url/whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], diff --git a/package.json b/package.json index f46add31..3de3340b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@clickhouse/click-ui": "0.9.1", - "@librechat/data-schemas": "^0.0.56", + "@librechat/data-schemas": "^0.0.74", "@radix-ui/react-dialog": "1.1.15", "@tailwindcss/vite": "^4.3.1", "@tanstack/react-devtools": "0.10.0", @@ -43,7 +43,7 @@ "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.4.2", "js-yaml": "^4.2.0", - "librechat-data-provider": "^0.8.509", + "librechat-data-provider": "^0.8.524", "lucide-react": "^0.545.0", "prom-client": "^15.1.3", "react": "^19.2.7", diff --git a/src/components/access/RolePermissionsPanel.tsx b/src/components/access/RolePermissionsPanel.tsx index 4c31f529..754bf22c 100644 --- a/src/components/access/RolePermissionsPanel.tsx +++ b/src/components/access/RolePermissionsPanel.tsx @@ -14,6 +14,7 @@ const PERMISSION_TYPE_ORDER: PermissionTypes[] = [ PermissionTypes.REMOTE_AGENTS, PermissionTypes.SKILLS, PermissionTypes.SHARED_LINKS, + PermissionTypes.SCHEDULES, PermissionTypes.BOOKMARKS, PermissionTypes.MULTI_CONVO, PermissionTypes.TEMPORARY_CHAT, diff --git a/src/components/configuration/configMeta.ts b/src/components/configuration/configMeta.ts index 7e4a6090..6c260a5a 100644 --- a/src/components/configuration/configMeta.ts +++ b/src/components/configuration/configMeta.ts @@ -98,6 +98,11 @@ export const SECTION_META: Record< descriptionKey: 'com_config_section_messageFilter_desc', tab: 'features', }, + filters: { + titleKey: 'com_config_section_filters', + descriptionKey: 'com_config_section_filters_desc', + tab: 'features', + }, langfuse: { titleKey: 'com_config_section_langfuse', descriptionKey: 'com_config_section_langfuse_desc', @@ -189,6 +194,16 @@ export const SECTION_META: Record< descriptionKey: 'com_config_section_skillSync_desc', tab: 'system', }, + permissions: { + titleKey: 'com_config_section_permissions', + descriptionKey: 'com_config_section_permissions_desc', + tab: 'system', + }, + openapi: { + titleKey: 'com_config_section_openapi', + descriptionKey: 'com_config_section_openapi_desc', + tab: 'system', + }, }; /** Sections omitted from the UI entirely (legacy fields pending removal from the schema). */ diff --git a/src/components/grants/AuditLogDetailDrawer.tsx b/src/components/grants/AuditLogDetailDrawer.tsx index 9c42eea2..9e4ddecc 100644 --- a/src/components/grants/AuditLogDetailDrawer.tsx +++ b/src/components/grants/AuditLogDetailDrawer.tsx @@ -1,5 +1,4 @@ import * as Dialog from '@radix-ui/react-dialog'; -import { PrincipalType } from 'librechat-data-provider'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Badge, Button, Icon, IconButton } from '@clickhouse/click-ui'; import type { ReactElement } from 'react'; @@ -7,12 +6,14 @@ import type * as t from '@/types'; import { ACTION_BADGE_STATE, ACTION_LABEL_KEY, - auditCapability, + ACTION_SUMMARY_KEY, + auditGrantee, + auditSubject, + auditTargetConfig, capabilityLabel, formatTimestamp, } from './auditLogUtils'; import { LoadingState } from '@/components/shared'; -import { getScopeTypeConfig } from '@/constants'; import { useLocalize } from '@/hooks'; import { cn } from '@/utils'; @@ -360,13 +361,10 @@ export function AuditLogDetailDrawer({ if (!latestEntry) return null; - const targetConfig = getScopeTypeConfig(latestEntry.target.type as PrincipalType); - const capability = auditCapability(latestEntry); + const targetConfig = auditTargetConfig(latestEntry.target.type); + const subject = auditSubject(latestEntry, localize); + const grantee = auditGrantee(latestEntry); const targetLabel = latestEntry.target.name ?? latestEntry.target.id ?? ''; - const summaryKey = - latestEntry.action === 'grant.assigned' - ? 'com_audit_detail_summary_assigned' - : 'com_audit_detail_summary_removed'; const before = latestEntry.before ?? []; const after = latestEntry.after ?? []; @@ -420,10 +418,11 @@ export function AuditLogDetailDrawer({

- {localize(summaryKey, { + {localize(ACTION_SUMMARY_KEY[latestEntry.action], { actor: latestEntry.actor.name, - capability: capabilityLabel(capability, localize), + capability: subject.label, target: targetLabel, + grantee: grantee ? `${grantee.type} ${grantee.id}` : '', })}

@@ -479,13 +478,26 @@ export function AuditLogDetailDrawer({
+ {grantee && ( + +
+ + {grantee.type} + + +
+
+ )} +
- - {capabilityLabel(capability, localize)} - + {subject.label} diff --git a/src/components/grants/AuditLogTab.tsx b/src/components/grants/AuditLogTab.tsx index ce2021e5..b0e6fed0 100644 --- a/src/components/grants/AuditLogTab.tsx +++ b/src/components/grants/AuditLogTab.tsx @@ -1,6 +1,6 @@ -import { PrincipalType } from 'librechat-data-provider'; import { useNavigate, useSearch } from '@tanstack/react-router'; import { keepPreviousData, useQuery } from '@tanstack/react-query'; +import { PrincipalType, ResourceType } from 'librechat-data-provider'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Badge, Button, DatePicker, Icon, Select, TextField } from '@clickhouse/click-ui'; import type { AuditAction } from '@librechat/data-schemas'; @@ -9,9 +9,9 @@ import type * as t from '@/types'; import { ACTION_BADGE_STATE, ACTION_LABEL_KEY, - auditCapability, + auditSubject, + auditTargetConfig, buildEntryPermalink, - capabilityLabel, dateToIsoDate, formatTimestamp, isoDateToDate, @@ -31,15 +31,15 @@ import { SearchInput, } from '@/components/shared'; import { useAnnouncement, useDebouncedFilter, useLocalize } from '@/hooks'; -import { getScopeTypeConfig, isAuditEntryId } from '@/constants'; import { AuditLogDetailDrawer } from './AuditLogDetailDrawer'; +import { AUDIT_ACTIONS, isAuditEntryId } from '@/constants'; import { cn } from '@/utils'; -const AUDIT_ACTIONS: readonly AuditAction[] = ['grant.assigned', 'grant.removed'] as const; -const TARGET_TYPE_OPTIONS: readonly PrincipalType[] = [ +const TARGET_TYPE_OPTIONS: readonly t.AuditTargetType[] = [ PrincipalType.USER, PrincipalType.GROUP, PrincipalType.ROLE, + ResourceType.AGENT, ] as const; /** Radix `Select.Item` cannot use `value=""` (Radix reserves empty string for * "no selection"). Use a non-empty sentinel and translate to `''` in state. */ @@ -109,7 +109,7 @@ export function AuditLogTab() { const [dateTo, setDateTo] = useState(''); /** Bumped each clear so DatePicker remounts and drops its internal selection state. */ const [dateResetNonce, setDateResetNonce] = useState(0); - const [targetTypeFilter, setTargetTypeFilter] = useState(''); + const [targetTypeFilter, setTargetTypeFilter] = useState(''); const [currentPage, setCurrentPage] = useState(1); const { message: announcement, announce } = useAnnouncement(); @@ -502,7 +502,7 @@ export function AuditLogTab() { label={localize('com_audit_filter_target_type')} value={targetTypeFilter === '' ? TARGET_TYPE_ALL : targetTypeFilter} onSelect={(v) => { - setTargetTypeFilter(v === TARGET_TYPE_ALL ? '' : (v as PrincipalType)); + setTargetTypeFilter(v === TARGET_TYPE_ALL ? '' : (v as t.AuditTargetType)); resetToFirstPage(); }} placeholder={localize('com_ui_all')} @@ -663,8 +663,8 @@ function AuditLogTableRow({ onKeyDown: (e: React.KeyboardEvent) => void; localize: ReturnType; }) { - const targetConfig = getScopeTypeConfig(entry.target.type as PrincipalType); - const capability = auditCapability(entry); + const targetConfig = auditTargetConfig(entry.target.type); + const subject = auditSubject(entry, localize); return (
- {capabilityLabel(capability, localize)} + {subject.label}
diff --git a/src/components/grants/auditLogUtils.test.ts b/src/components/grants/auditLogUtils.test.ts index 839eb206..a5723a20 100644 --- a/src/components/grants/auditLogUtils.test.ts +++ b/src/components/grants/auditLogUtils.test.ts @@ -1,7 +1,12 @@ import { describe, it, expect } from 'vitest'; import { ACTION_BADGE_STATE, + ACTION_LABEL_KEY, + ACTION_SUMMARY_KEY, auditCapability, + auditGrantee, + auditSubject, + auditTargetConfig, buildEntryPermalink, capabilityLabel, dateToIsoDate, @@ -9,6 +14,8 @@ import { isoDateToDate, localDayBoundaryIso, } from './auditLogUtils'; +import translation from '@/locales/en/translation.json'; +import { AUDIT_ACTIONS } from '@/constants'; const identityLocalize = (k: string) => k; @@ -16,6 +23,60 @@ describe('ACTION_BADGE_STATE', () => { it('maps each audit action to a badge state', () => { expect(ACTION_BADGE_STATE['grant.assigned']).toBe('success'); expect(ACTION_BADGE_STATE['grant.removed']).toBe('danger'); + expect(ACTION_BADGE_STATE['permission.insights_assigned']).toBe('success'); + expect(ACTION_BADGE_STATE['permission.insights_removed']).toBe('danger'); + }); + + it('has a localized label and summary for every audit action', () => { + const localeKeys = new Set(Object.keys(translation)); + for (const action of AUDIT_ACTIONS) { + expect(localeKeys.has(ACTION_LABEL_KEY[action])).toBe(true); + expect(localeKeys.has(ACTION_SUMMARY_KEY[action])).toBe(true); + } + }); +}); + +describe('auditSubject', () => { + it('uses the capability for grant entries', () => { + expect( + auditSubject( + { action: 'grant.assigned', metadata: { capability: 'manage:users' } }, + identityLocalize, + ), + ).toEqual({ label: 'manage:users', value: 'manage:users' }); + }); + + it('reports the VIEW_INSIGHTS permission for Insights entries', () => { + expect( + auditSubject( + { action: 'permission.insights_removed', metadata: { principalType: 'user' } }, + identityLocalize, + ), + ).toEqual({ label: 'com_audit_insights_permission', value: 'VIEW_INSIGHTS' }); + }); +}); + +describe('auditGrantee', () => { + it('reads the affected principal from metadata', () => { + expect(auditGrantee({ metadata: { principalType: 'group', principalId: 'g1' } })).toEqual({ + type: 'group', + id: 'g1', + }); + }); + + it('returns undefined for grant entries without principal metadata', () => { + expect(auditGrantee({ metadata: { capability: 'manage:users' } })).toBeUndefined(); + expect(auditGrantee({ metadata: undefined })).toBeUndefined(); + }); +}); + +describe('auditTargetConfig', () => { + it('labels agent targets as agents', () => { + expect(auditTargetConfig('agent').labelKey).toBe('com_audit_target_agent'); + }); + + it('uses the scope config for principal targets', () => { + expect(auditTargetConfig('role').labelKey).toBe('com_scope_roles'); }); }); diff --git a/src/components/grants/auditLogUtils.ts b/src/components/grants/auditLogUtils.ts index 0ef2bfdf..a1dd56f6 100644 --- a/src/components/grants/auditLogUtils.ts +++ b/src/components/grants/auditLogUtils.ts @@ -1,4 +1,8 @@ +import { ResourceType } from 'librechat-data-provider'; import type { AdminAuditLogEntry, AuditAction } from '@librechat/data-schemas'; +import type { PrincipalType } from 'librechat-data-provider'; +import type * as t from '@/types'; +import { getScopeTypeConfig } from '@/constants'; /** The capability a grant entry concerns now lives in `metadata.capability` * (other event categories omit it). Returns '' when absent or non-string. */ @@ -10,13 +14,65 @@ export function auditCapability(entry: Pick): st export const ACTION_BADGE_STATE: Record = { 'grant.assigned': 'success', 'grant.removed': 'danger', + 'permission.insights_assigned': 'success', + 'permission.insights_removed': 'danger', }; export const ACTION_LABEL_KEY: Record = { 'grant.assigned': 'com_audit_action_assigned', 'grant.removed': 'com_audit_action_removed', + 'permission.insights_assigned': 'com_audit_action_insights_assigned', + 'permission.insights_removed': 'com_audit_action_insights_removed', }; +export const ACTION_SUMMARY_KEY: Record = { + 'grant.assigned': 'com_audit_detail_summary_assigned', + 'grant.removed': 'com_audit_detail_summary_removed', + 'permission.insights_assigned': 'com_audit_detail_summary_insights_assigned', + 'permission.insights_removed': 'com_audit_detail_summary_insights_removed', +}; + +const INSIGHTS_PERMISSION = 'VIEW_INSIGHTS'; + +function isInsightsAction(action: AuditAction): boolean { + return action === 'permission.insights_assigned' || action === 'permission.insights_removed'; +} + +/** Capability grants carry `metadata.capability`; Insights entries change the + * agent-level `VIEW_INSIGHTS` permission bit instead. */ +export function auditSubject( + entry: Pick, + localize: (key: string) => string, +): t.AuditSubject { + if (isInsightsAction(entry.action)) { + return { label: localize('com_audit_insights_permission'), value: INSIGHTS_PERMISSION }; + } + const capability = auditCapability(entry); + return { label: capabilityLabel(capability, localize), value: capability }; +} + +/** Insights entries target the agent; the affected principal lives in metadata. */ +export function auditGrantee( + entry: Pick, +): t.AuditGrantee | undefined { + const type = entry.metadata?.principalType; + const id = entry.metadata?.principalId; + if (typeof type !== 'string' || typeof id !== 'string') return undefined; + return { type, id }; +} + +const AGENT_TARGET_CONFIG: Pick = { + icon: 'sparkle', + labelKey: 'com_audit_target_agent', +}; + +export function auditTargetConfig( + targetType: string, +): Pick { + if (targetType === ResourceType.AGENT) return AGENT_TARGET_CONFIG; + return getScopeTypeConfig(targetType as PrincipalType); +} + /** Parse a `YYYY-MM-DD` filter value as a local-time date so the DatePicker * round-trips the same calendar day the user picked, regardless of TZ. * Rejects rolled-over inputs like `2026-13-01` (which `Date` would silently diff --git a/src/components/grants/index.ts b/src/components/grants/index.ts index 63d8702f..1660cd7b 100644 --- a/src/components/grants/index.ts +++ b/src/components/grants/index.ts @@ -1,6 +1,7 @@ export { ACTION_BADGE_STATE, ACTION_LABEL_KEY, + ACTION_SUMMARY_KEY, capabilityLabel, formatTimestamp, } from './auditLogUtils'; diff --git a/src/constants/audit.ts b/src/constants/audit.ts index d289eaa2..ba5cb0b3 100644 --- a/src/constants/audit.ts +++ b/src/constants/audit.ts @@ -22,6 +22,8 @@ import type { export const AUDIT_ACTIONS = [ 'grant.assigned', 'grant.removed', + 'permission.insights_assigned', + 'permission.insights_removed', ] as const satisfies readonly AuditAction[]; export const AUDIT_CATEGORIES = [ diff --git a/src/constants/role.ts b/src/constants/role.ts index dd64c495..386b83e2 100644 --- a/src/constants/role.ts +++ b/src/constants/role.ts @@ -55,6 +55,7 @@ export const PERMISSION_TYPE_SCHEMA: Record = { Permissions.SHARE_PUBLIC, ], [PermissionTypes.SHARED_LINKS]: [Permissions.CREATE, Permissions.SHARE, Permissions.SHARE_PUBLIC], + [PermissionTypes.SCHEDULES]: [Permissions.USE, Permissions.CREATE], }; export function defaultPermissions(): t.RolePermissions { diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e3922958..250ec124 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -697,6 +697,213 @@ "com_config_field_guardrailIdentifier": "Guardrail identifier", "com_config_field_guardrailVersion": "Guardrail version", "com_config_field_trace": "Trace", + "com_config_field_action": "Action", + "com_config_field_actionMetadata": "Action metadata", + "com_config_field_activityCharLimit": "Activity character limit", + "com_config_field_activityEndpoint": "Activity endpoint", + "com_config_field_activityLabel": "Activity labels", + "com_config_field_activityMaxPerRun": "Activity labels per run", + "com_config_field_activityModel": "Activity model", + "com_config_field_activityPhaseEndpoint": "Activity phase endpoint", + "com_config_field_activityPhaseLabel": "Activity phase labels", + "com_config_field_activityPhaseMaxPerRun": "Activity phase labels per run", + "com_config_field_activityPhaseModel": "Activity phase model", + "com_config_field_activityPhasePrompt": "Activity phase prompt", + "com_config_field_activityPrompt": "Activity prompt", + "com_config_field_admissionConcurrency": "Admission concurrency", + "com_config_field_agentEvents": "Agent events", + "com_config_field_agentInstructions": "Agent instructions", + "com_config_field_allow": "Allow", + "com_config_field_allowPrincipalWorkers": "Allow principal workers", + "com_config_field_allowSiblingSharing": "Allow sibling sharing", + "com_config_field_allow_item": "tool pattern", + "com_config_field_allowed": "Allowed", + "com_config_field_allowedEnvironments": "Allowed environments", + "com_config_field_allowedEnvironments_item": "environment", + "com_config_field_allowed_item": "mode", + "com_config_field_archivedAt": "Archived at", + "com_config_field_ask": "Ask", + "com_config_field_askUserQuestion": "Ask user question", + "com_config_field_ask_item": "tool pattern", + "com_config_field_attributionTitle": "Attribution title", + "com_config_field_authorizationFenceRetryBatchSize": "Authorization fence retry batch size", + "com_config_field_authorizationFenceRetryIntervalMs": "Authorization fence retry interval (ms)", + "com_config_field_authorizationFenceRetryMs": "Authorization fence retry delays (ms)", + "com_config_field_authorizationFenceRetryMs_item": "delay", + "com_config_field_authorizationFenceTimeoutMs": "Authorization fence timeout (ms)", + "com_config_field_autoDisableAfterFailures": "Auto-disable after failures", + "com_config_field_backgroundTasks": "Background tasks", + "com_config_field_catalogRecovery": "Catalog recovery", + "com_config_field_checkpointCollectionName": "Checkpoint collection name", + "com_config_field_checkpointWritesCollectionName": "Checkpoint writes collection name", + "com_config_field_checkpointer": "Checkpointer", + "com_config_field_clientId": "Client ID", + "com_config_field_clients": "Clients", + "com_config_field_clients_item": "client", + "com_config_field_codeApiMaxRetryWaitMs": "Code API max retry wait (ms)", + "com_config_field_codeApiUploadConcurrency": "Code API upload concurrency", + "com_config_field_codeApprovalMode": "Code approval mode", + "com_config_field_codeEnvLivenessSafeWindowMs": "Code environment liveness window (ms)", + "com_config_field_codeEnvironmentMode": "Code environment mode", + "com_config_field_codeHighlightThrottleMs": "Code highlight throttle (ms)", + "com_config_field_codeWorkspaces": "Code workspaces", + "com_config_field_codeWorkspaces_item": "workspace", + "com_config_field_commandExecution": "Command execution", + "com_config_field_completionResultBatchSize": "Completion result batch size", + "com_config_field_completionResultMaxChars": "Completion result max characters", + "com_config_field_completionWakeups": "Completion wakeups", + "com_config_field_configSchema": "Config schema", + "com_config_field_conversationMetadataFields": "Conversation metadata fields", + "com_config_field_conversationMetadataFields_item": "field", + "com_config_field_conversationMoves": "Conversation moves", + "com_config_field_conversationStarters": "Conversation starters", + "com_config_field_conversationTitles": "Conversation titles", + "com_config_field_cwd": "Working directory", + "com_config_field_defaultLLMDeliveryPath": "Default LLM delivery path", + "com_config_field_deny": "Deny", + "com_config_field_deny_item": "tool pattern", + "com_config_field_depth": "Depth", + "com_config_field_describeIntent": "Describe intent", + "com_config_field_destination": "Destination", + "com_config_field_discoveryBackoffMs": "Discovery backoff (ms)", + "com_config_field_discoveryBackoffMs_item": "delay", + "com_config_field_discoverySettleGraceMs": "Discovery settle grace (ms)", + "com_config_field_discoveryTimeoutMs": "Discovery timeout (ms)", + "com_config_field_engines": "Engines", + "com_config_field_environmentId": "Environment ID", + "com_config_field_environments": "Environments", + "com_config_field_environments_item": "environment", + "com_config_field_eventDriven": "Event-driven", + "com_config_field_fallback": "Fallback", + "com_config_field_feedback": "Feedback", + "com_config_field_fields": "Fields", + "com_config_field_fields_item": "field", + "com_config_field_fileContextCharLimit": "File context character limit", + "com_config_field_fileContextSizeLimit": "File context size limit", + "com_config_field_fileSharing": "File sharing", + "com_config_field_fileWrite": "File write", + "com_config_field_files": "Files", + "com_config_field_filters": "Filters", + "com_config_field_fireConcurrency": "Fire concurrency", + "com_config_field_generalChatRetention": "General chat retention", + "com_config_field_generationReadTimeoutMs": "Generation read timeout (ms)", + "com_config_field_hooks": "Hooks", + "com_config_field_hooks_item": "hook", + "com_config_field_importCleanupConcurrency": "Import cleanup concurrency", + "com_config_field_isShared": "Shared", + "com_config_field_keenableApiKey": "Keenable API key", + "com_config_field_keenableApiUrl": "Keenable API URL", + "com_config_field_keenableScraperOptions": "Keenable scraper options", + "com_config_field_keenableSearchOptions": "Keenable search options", + "com_config_field_langfuse": "Langfuse", + "com_config_field_legacyFileUploadUX": "Legacy file upload UX", + "com_config_field_limits": "Limits", + "com_config_field_managementApi": "Management API", + "com_config_field_matcher": "Matcher", + "com_config_field_maxCommandTimeoutMs": "Max command timeout (ms)", + "com_config_field_maxContentLength": "Max content length", + "com_config_field_maxDeltaEventsPerTurn": "Max delta events per turn", + "com_config_field_maxDetachedDiscoveries": "Max detached discoveries", + "com_config_field_maxFiles": "Max files", + "com_config_field_maxPerUser": "Max per user", + "com_config_field_maxPrivateBytes": "Max private bytes", + "com_config_field_maxProviderErrorChars": "Max provider error characters", + "com_config_field_maxQueueWaitMs": "Max queue wait (ms)", + "com_config_field_maxRecords": "Max records", + "com_config_field_maxRetainedToolCountChars": "Max retained tool count characters", + "com_config_field_maxStateEntries": "Max state entries", + "com_config_field_maxSubagents": "Max subagents", + "com_config_field_maxToolCallArgBytes": "Max tool call argument bytes", + "com_config_field_maxToolCallArgBytesByTool": "Max tool call argument bytes by tool", + "com_config_field_maxWriteAttempts": "Max write attempts", + "com_config_field_mcpPreflightConcurrency": "MCP preflight concurrency", + "com_config_field_mcpPreflightTimeoutMs": "MCP preflight timeout (ms)", + "com_config_field_minIntervalMinutes": "Min interval (minutes)", + "com_config_field_mode": "Mode", + "com_config_field_modelParameters": "Model parameters", + "com_config_field_modelResponseBodyTimeoutMs": "Model response body timeout (ms)", + "com_config_field_modelResponseHeadersTimeoutMs": "Model response headers timeout (ms)", + "com_config_field_module": "Module", + "com_config_field_oauthPersistenceWaitTimeout": "OAuth persistence wait timeout", + "com_config_field_oauthRefreshCoordination": "OAuth refresh coordination", + "com_config_field_oauthRefreshWaitTimeout": "OAuth refresh wait timeout", + "com_config_field_oauthStateTtlMs": "OAuth state TTL (ms)", + "com_config_field_openapi": "OpenAPI", + "com_config_field_openidDiscovery": "OpenID discovery", + "com_config_field_ordinaryToolCancellation": "Ordinary tool cancellation", + "com_config_field_overrides": "Overrides", + "com_config_field_owner": "Owner", + "com_config_field_pairing": "Pairing", + "com_config_field_parentAgentId": "Parent agent ID", + "com_config_field_parentConversationId": "Parent conversation ID", + "com_config_field_parentToolCallId": "Parent tool call ID", + "com_config_field_permissions": "Permissions", + "com_config_field_positiveMin": "Positive minimum", + "com_config_field_principalWorkers": "Principal workers", + "com_config_field_publicKey": "Public key", + "com_config_field_reason": "Reason", + "com_config_field_reasoningLabel": "Reasoning labels", + "com_config_field_reasoningLabelEndpoint": "Reasoning label endpoint", + "com_config_field_reasoningLabelMaxPerRun": "Reasoning labels per run", + "com_config_field_reasoningLabelMinChars": "Reasoning label min characters", + "com_config_field_reasoningLabelModel": "Reasoning label model", + "com_config_field_reasoningLabelPrompt": "Reasoning label prompt", + "com_config_field_reasoningLabelUpdateChars": "Reasoning label update characters", + "com_config_field_reasoningLabelUpdateIntervalMs": "Reasoning label update interval (ms)", + "com_config_field_reasoning_context": "Reasoning context", + "com_config_field_reasoning_mode": "Reasoning mode", + "com_config_field_reauthRetryMs": "Reauthentication retry (ms)", + "com_config_field_repositoryInstructions": "Repository instructions", + "com_config_field_requestHeaders": "Request headers", + "com_config_field_requestTimeoutMs": "Request timeout (ms)", + "com_config_field_requestsPerMinute": "Requests per minute", + "com_config_field_requireProject": "Require project", + "com_config_field_requiredScopes": "Required scopes", + "com_config_field_requiredScopes_item": "scope", + "com_config_field_reservationTtlMs": "Reservation TTL (ms)", + "com_config_field_retainRecent": "Retain recent", + "com_config_field_retainedAnswers": "Retained answers", + "com_config_field_retryDelayMs": "Retry delay (ms)", + "com_config_field_rootConversationId": "Root conversation ID", + "com_config_field_runInBackground": "Run in background", + "com_config_field_schedules": "Schedules", + "com_config_field_searxngSearchOptions": "SearXNG search options", + "com_config_field_secretKey": "Secret key", + "com_config_field_selfUrl": "Self URL", + "com_config_field_send_resource_parameter": "Send resource parameter", + "com_config_field_settings": "Settings", + "com_config_field_shareFiles": "Share files", + "com_config_field_showInMenu": "Show in menu", + "com_config_field_showInputOutput": "Show input and output", + "com_config_field_showToolNames": "Show tool names", + "com_config_field_site": "Site", + "com_config_field_startupAttempts": "Startup attempts", + "com_config_field_statefulCodeSessions": "Stateful code sessions", + "com_config_field_statusRefreshInterval": "Status refresh interval", + "com_config_field_subagentKind": "Subagent kind", + "com_config_field_subagentThread": "Subagent thread", + "com_config_field_subagentType": "Subagent type", + "com_config_field_subject": "Subject", + "com_config_field_tenantId": "Tenant ID", + "com_config_field_textFallbackWithoutTools": "Text fallback without tools", + "com_config_field_tokenEnv": "Token environment variable", + "com_config_field_tokenUse": "Token use", + "com_config_field_tokens": "Tokens", + "com_config_field_toolApproval": "Tool approval", + "com_config_field_toolArguments": "Tool arguments", + "com_config_field_toolsRefreshInterval": "Tools refresh interval", + "com_config_field_traceViewer": "Trace viewer", + "com_config_field_ttl": "TTL", + "com_config_field_ttlMs": "TTL (ms)", + "com_config_field_turns": "Turns", + "com_config_field_unattributedAssistantContent": "Unattributed assistant content", + "com_config_field_uninspectable": "Uninspectable files", + "com_config_field_userId": "User ID", + "com_config_field_userIdField": "User ID field", + "com_config_field_userMetadataFields": "User metadata fields", + "com_config_field_userMetadataFields_item": "field", + "com_config_field_workerId": "Worker ID", + "com_config_field_workspaceId": "Workspace ID", "com_config_field_streamProcessingMode": "Stream processing mode", "com_config_field_allowedAddresses_item": "address", "com_config_field_includeDomains_item": "domain", @@ -766,6 +973,8 @@ "com_config_section_includedTools_desc": "Tools explicitly included for availability", "com_config_section_messageFilter": "Message filter", "com_config_section_messageFilter_desc": "Filter or block patterns in user messages", + "com_config_section_filters": "Content filters", + "com_config_section_filters_desc": "Detect and block or audit PII in messages, prompts, files, and other content", "com_config_section_langfuse": "Langfuse", "com_config_section_langfuse_desc": "Connect this tenant to a Langfuse project to export traces and feedback scores", "com_config_langfuse_enabled": "Langfuse connection", @@ -789,6 +998,10 @@ "com_config_section_secureImageLinks_desc": "Enable secure, authenticated image URL delivery", "com_config_section_skillSync": "Skill sync", "com_config_section_skillSync_desc": "Periodically pull skill definitions from external sources", + "com_config_section_permissions": "Permissions", + "com_config_section_permissions_desc": "Retry limits for concurrent permission writes", + "com_config_section_openapi": "OpenAPI", + "com_config_section_openapi_desc": "Serve the OpenAPI spec and docs for the public Agents API", "com_config_section_summarization": "Summarization", "com_config_section_summarization_desc": "Configure conversation summarization behavior", "com_endpoint_prompt_cache": "Prompt cache", @@ -1031,6 +1244,7 @@ "com_perm_desc_REMOTE_AGENTS": "Connect and manage remote agent endpoints", "com_perm_desc_SKILLS": "Create, share, and use agent skills", "com_perm_desc_SHARED_LINKS": "Create and share conversation share links", + "com_perm_desc_SCHEDULES": "Create and run scheduled agent tasks", "com_perm_desc_PEOPLE_PICKER": "Control visibility of users, groups, and roles", "com_perm_type_BOOKMARKS": "Bookmarks", "com_perm_type_PROMPTS": "Prompts", @@ -1048,6 +1262,7 @@ "com_perm_type_REMOTE_AGENTS": "Remote agents", "com_perm_type_SKILLS": "Skills", "com_perm_type_SHARED_LINKS": "Shared links", + "com_perm_type_SCHEDULES": "Schedules", "com_perm_USE": "Use", "com_perm_CREATE": "Create", "com_perm_UPDATE": "Update", @@ -1135,6 +1350,8 @@ "com_cap_read_assistants": "Read assistants", "com_cap_manage_assistants": "Manage assistants", "com_cap_read_audit_log": "Read audit log", + "com_cap_read_insights": "Read insights", + "com_cap_manage_code_environments": "Manage code environments", "com_cap_desc_access_admin": "Full admin panel access", "com_cap_desc_read_users": "View user list and profiles", "com_cap_desc_manage_users": "Create, edit, and delete users", @@ -1158,6 +1375,8 @@ "com_cap_desc_read_assistants": "View assistant configurations", "com_cap_desc_manage_assistants": "Create, edit, and delete assistants", "com_cap_desc_read_audit_log": "View and export the audit log of grant changes", + "com_cap_desc_read_insights": "View agent usage insights", + "com_cap_desc_manage_code_environments": "Manage stateful code environments", "com_access_denied_title": "Access denied", "com_access_denied_description": "You don't have permission to view this page. Contact your administrator.", "com_cap_no_permission": "You need {{cap}} to perform this action", @@ -1173,6 +1392,10 @@ "com_audit_col_capability": "Capability", "com_audit_action_assigned": "Granted", "com_audit_action_removed": "Revoked", + "com_audit_action_insights_assigned": "Insights granted", + "com_audit_action_insights_removed": "Insights revoked", + "com_audit_insights_permission": "View insights", + "com_audit_target_agent": "Agent", "com_audit_empty": "No audit log entries found", "com_audit_entry_count_zero": "No entries", "com_audit_entry_count_one": "{{count}} entry", @@ -1191,9 +1414,12 @@ "com_audit_detail_title": "Audit log entry", "com_audit_detail_summary_assigned": "{{actor}} granted {{capability}} to {{target}}", "com_audit_detail_summary_removed": "{{actor}} revoked {{capability}} from {{target}}", + "com_audit_detail_summary_insights_assigned": "{{actor}} granted {{capability}} on {{target}} to {{grantee}}", + "com_audit_detail_summary_insights_removed": "{{actor}} revoked {{capability}} on {{target}} from {{grantee}}", "com_audit_detail_actor": "Actor", "com_audit_detail_target": "Target", "com_audit_detail_capability": "Capability", + "com_audit_detail_grantee": "Grantee", "com_audit_detail_timestamp": "When", "com_audit_detail_entry_id": "Entry ID", "com_audit_detail_before": "Before", diff --git a/src/server/config.test.ts b/src/server/config.test.ts index 797c3d15..6c24ac9e 100644 --- a/src/server/config.test.ts +++ b/src/server/config.test.ts @@ -79,18 +79,6 @@ function findField(fields: t.SchemaField[], key: string): t.SchemaField | undefi } describe('applyLangfuseSchemaVisibility', () => { - it('injects the Langfuse section when fanout is enabled and the pinned schema lacks it', () => { - const tree = extractSchemaTree(z3.object({ interface: z3.object({ theme: z3.string() }) })); - - applyLangfuseSchemaVisibility(tree, true); - - expect(findField(tree, 'langfuse')).toMatchObject({ - key: 'langfuse', - isObject: true, - }); - expect(findField(tree, 'destination')?.path).toBe('langfuse.destination'); - }); - it('removes a Langfuse section supplied by the schema when fanout is disabled', () => { const tree = extractSchemaTree(z3.object({ langfuse: z3.object({ enabled: z3.boolean() }) })); @@ -120,12 +108,13 @@ describe('applyLangfuseSchemaVisibility', () => { expect(findField(tree, 'schemaOnlyField')).toBeDefined(); }); - it('does not inject the compatibility shim when fanout state is unknown', () => { - const tree = extractSchemaTree(z3.object({ interface: z3.object({ theme: z3.string() }) })); + it('keeps the Langfuse section shipped in the pinned configSchema', () => { + const tree = extractSchemaTree(realConfigSchema); - applyLangfuseSchemaVisibility(tree, undefined); + applyLangfuseSchemaVisibility(tree, true); - expect(findField(tree, 'langfuse')).toBeUndefined(); + expect(findField(tree, 'langfuse')?.isObject).toBe(true); + expect(findField(tree, 'destination')?.path).toBe('langfuse.destination'); }); }); @@ -785,8 +774,23 @@ const SAMPLE_OVERRIDES: Record = { 'mcpSettings.allowedAddresses': ['localhost:11434'], 'actions.allowedAddresses': ['localhost:11434'], 'endpoints.allowedAddresses': ['localhost:11434'], + 'ocr.allowedAddresses': ['localhost:11434'], + 'webSearch.allowedAddresses': ['localhost:11434'], + 'speech.tts.allowedAddresses': ['localhost:11434'], + 'speech.stt.allowedAddresses': ['localhost:11434'], 'endpoints.agents.remoteApi.auth.oidc.issuer': 'https://example.com', 'endpoints.agents.remoteApi.auth.oidc.jwksUri': 'https://example.com/.well-known/jwks.json', + 'endpoints.agents.managementApi.auth.oidc.issuer': 'https://example.com', + 'endpoints.agents.managementApi.auth.oidc.jwksUri': 'https://example.com/.well-known/jwks.json', + 'endpoints.agents.managementApi.auth.oidc.tokenUse': 'access', + 'endpoints.agents.eventDriven.selfUrl': 'https://example.com', + 'endpoints.agents.repositoryInstructions.timeoutMs': 1000, + 'endpoints.agents.maxToolCallArgBytesByTool': { sample_tool: 1024 }, + 'fileConfig.defaultLLMDeliveryPath.overrides': { 'application/pdf': 'text' }, + 'interface.traceViewer.requestTimeoutMs': 1000, + 'registration.oauthStateTtlMs': 60000, + 'registration.openidDiscovery.retryDelayMs': 100, + 'balance.reservationTtlMs': 10000, 'summarization.trigger': { type: 'token_ratio', value: 0.5 }, 'skillSync.github.intervalMinutes': 5, 'skillSync.github.sources': [ diff --git a/src/server/config.ts b/src/server/config.ts index fa0e6236..15384ef2 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -22,49 +22,15 @@ import { safeFieldPath } from './utils/validation'; import { flattenObject } from '@/utils/format'; import { apiFetch } from './utils/api'; -/** - * Forward-compat shim: the pinned `librechat-data-provider@^0.8.509` predates the - * `langfuse` config group. Inject the section node so the custom renderer remains - * discoverable until a data-provider release containing the group is pinned. The - * renderer persists through LibreChat's dedicated Langfuse connection API. - */ -const LANGFUSE_SHIM_FIELD: t.SchemaField = { - path: 'langfuse', - key: 'langfuse', - type: 'object', - isOptional: true, - isNullable: false, - isArray: false, - isObject: true, - depth: 0, - children: (['enabled', 'destination', 'publicKey', 'secretKey', 'displaySecretKey'] as const).map( - (key) => ({ - path: `langfuse.${key}`, - key, - type: key === 'enabled' ? 'boolean' : 'string', - isOptional: true, - isNullable: false, - isArray: false, - isObject: false, - depth: 1, - }), - ), -}; - +/** Hide the Langfuse section when LibreChat reports fanout is disabled. When the + * fanout state is unknown (older backends), the schema-provided section is kept. */ export function applyLangfuseSchemaVisibility( tree: t.SchemaField[], fanoutEnabled: boolean | undefined, ): t.SchemaField[] { + if (fanoutEnabled !== false) return tree; const langfuseIndex = tree.findIndex((section) => section.key === 'langfuse'); - if (fanoutEnabled === false) { - if (langfuseIndex >= 0) { - tree.splice(langfuseIndex, 1); - } - return tree; - } - if (fanoutEnabled === true && langfuseIndex < 0) { - tree.push(LANGFUSE_SHIM_FIELD); - } + if (langfuseIndex >= 0) tree.splice(langfuseIndex, 1); return tree; } diff --git a/src/types/grant.ts b/src/types/grant.ts index be43f91d..2ca51aa2 100644 --- a/src/types/grant.ts +++ b/src/types/grant.ts @@ -1,5 +1,5 @@ +import type { PrincipalType, ResourceType } from 'librechat-data-provider'; import type { AdminAuditLogEntry } from '@librechat/data-schemas'; -import type { PrincipalType } from 'librechat-data-provider'; import type { KeyboardEvent } from 'react'; export interface AuditLogEntryWithDiff extends AdminAuditLogEntry { @@ -7,6 +7,25 @@ export interface AuditLogEntryWithDiff extends AdminAuditLogEntry { after?: readonly string[]; } +/** `target.type` values the audit log can filter on: principals for grants, agents for Insights. */ +export type AuditTargetType = + | PrincipalType.USER + | PrincipalType.GROUP + | PrincipalType.ROLE + | ResourceType.AGENT; + +/** What an audit entry changed: a system capability, or an agent-level permission. */ +export interface AuditSubject { + label: string; + value: string; +} + +/** Principal whose agent-level permission an `permission.*` audit entry changed. */ +export interface AuditGrantee { + type: string; + id: string; +} + export interface PrincipalRow { principalType: PrincipalType; principalId: string; diff --git a/src/utils/interfacePermissions.test.ts b/src/utils/interfacePermissions.test.ts new file mode 100644 index 00000000..be12ca8b --- /dev/null +++ b/src/utils/interfacePermissions.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import type { TInterfaceConfig } from 'librechat-data-provider'; +import { isInterfacePermissionPath, stripInterfacePermissionFields } from './interfacePermissions'; + +describe('isInterfacePermissionPath', () => { + it('blocks bare permission fields and permission sub-keys', () => { + expect(isInterfacePermissionPath('interface.prompts')).toBe(true); + expect(isInterfacePermissionPath('interface.mcpServers')).toBe(true); + expect(isInterfacePermissionPath('interface.mcpServers.use')).toBe(true); + }); + + it('allows UI sub-keys and non-permission fields', () => { + expect(isInterfacePermissionPath('interface.mcpServers.placeholder')).toBe(false); + expect(isInterfacePermissionPath('interface.endpointsMenu')).toBe(false); + }); + + it('allows the bare runtime toggle for schedules but blocks its permission sub-keys', () => { + expect(isInterfacePermissionPath('interface.schedules')).toBe(false); + expect(isInterfacePermissionPath('interface.schedules.maxPerUser')).toBe(false); + expect(isInterfacePermissionPath('interface.schedules.use')).toBe(true); + expect(isInterfacePermissionPath('interface.schedules.create')).toBe(true); + }); +}); + +describe('stripInterfacePermissionFields', () => { + it('drops boolean permission fields', () => { + expect(stripInterfacePermissionFields({ prompts: true, bookmarks: false })).toEqual({}); + }); + + it('preserves the boolean runtime toggle for schedules', () => { + expect(stripInterfacePermissionFields({ schedules: false })).toEqual({ schedules: false }); + }); + + it('strips permission sub-keys from the schedules object form', () => { + const input = { + schedules: { use: true, create: true, maxPerUser: 2 }, + } as Partial; + expect(stripInterfacePermissionFields(input)).toEqual({ schedules: { maxPerUser: 2 } }); + }); + + it('collapses a disabled schedules object to false', () => { + const input = { schedules: { use: false, maxPerUser: 2 } } as Partial; + expect(stripInterfacePermissionFields(input)).toEqual({ schedules: false }); + }); +}); diff --git a/src/utils/interfacePermissions.ts b/src/utils/interfacePermissions.ts index bbd4fdf4..3066a818 100644 --- a/src/utils/interfacePermissions.ts +++ b/src/utils/interfacePermissions.ts @@ -1,11 +1,12 @@ import { INTERFACE_PERMISSION_FIELDS, PERMISSION_SUB_KEYS, + RUNTIME_CONFIG_INTERFACE_FIELDS, } from 'librechat-data-provider'; import type { TInterfaceConfig } from 'librechat-data-provider'; import type * as t from '@/types'; -export { INTERFACE_PERMISSION_FIELDS, PERMISSION_SUB_KEYS }; +export { INTERFACE_PERMISSION_FIELDS, PERMISSION_SUB_KEYS, RUNTIME_CONFIG_INTERFACE_FIELDS }; /** Returns true if a dot-path should be blocked from config override writes. * @@ -19,21 +20,28 @@ export { INTERFACE_PERMISSION_FIELDS, PERMISSION_SUB_KEYS }; * - `interface.mcpServers.use` → true (permission sub-key, blocked) * - `interface.mcpServers.placeholder` → false (UI sub-key, allowed) * - `interface.peoplePicker.users` → true (permission sub-key, blocked) + * - `interface.schedules` → false (runtime enable toggle, see `RUNTIME_CONFIG_INTERFACE_FIELDS`) + * - `interface.schedules.use` → true (permission sub-key, blocked) * - `interface.endpointsMenu` → false (pure UI field) */ export function isInterfacePermissionPath(fieldPath: string): boolean { const segments = fieldPath.split('.'); if (segments[0] !== 'interface' || segments.length < 2) return false; if (!INTERFACE_PERMISSION_FIELDS.has(segments[1])) return false; // Bare field path (e.g. `interface.prompts` or `interface.mcpServers`) — - // blocked because writing the whole field could include permission bits. - if (segments.length === 2) return true; + // blocked because writing the whole field could include permission bits, + // except dual-purpose runtime fields whose bare value is a feature toggle. + if (segments.length === 2) return !RUNTIME_CONFIG_INTERFACE_FIELDS.has(segments[1]); // Sub-key path — only block if the sub-key is a permission bit return PERMISSION_SUB_KEYS.has(segments[2]); } /** Strips permission fields and permission sub-keys from an interface config * object. Boolean permission fields are removed entirely; composite permission - * fields have their permission sub-keys stripped while UI sub-keys pass through. */ + * fields have their permission sub-keys stripped while UI sub-keys pass through. + * + * Runtime fields (`RUNTIME_CONFIG_INTERFACE_FIELDS`) mirror LibreChat's override + * sanitizer: the boolean form is a feature toggle and is preserved, and an object + * with `use: false` collapses to `false` so stripping `use` cannot re-enable it. */ export function stripInterfacePermissionFields( obj: Partial, ): Partial { @@ -43,6 +51,15 @@ export function stripInterfacePermissionFields( (result as Record)[key] = value; continue; } + const isRuntimeField = RUNTIME_CONFIG_INTERFACE_FIELDS.has(key); + if (isRuntimeField && typeof value === 'boolean') { + (result as Record)[key] = value; + continue; + } + if (isRuntimeField && (value as Record | null)?.use === false) { + (result as Record)[key] = false; + continue; + } // Composite field — strip permission sub-keys, keep UI sub-keys if (value && typeof value === 'object' && !Array.isArray(value)) { const filtered: Record = {};