16 Commits
Author SHA1 Message Date
mytheclipsebotreview[bot] ae3b22df93 Auto-merge PR #68
build(deps-dev): bump vitest from 5.0.0 to 5.0.1 in /services/backend in the development group
2026-09-18 17:21:44 +00:00
mytheclipsebotreview[bot] 4f7e586ec3 Auto-merge PR #67
build(deps): bump @orpc/server from 1.15.0 to 1.15.1 in /services/backend in the production group
2026-09-18 17:21:35 +00:00
mytheclipsebotreview[bot] fc3c80d73d Auto-merge PR #66
build(deps): bump @orpc/client from 1.15.0 to 1.15.1 in /services/frontend in the production group
2026-09-18 17:15:20 +00:00
asepharyana 43e35a71dd test: add live-LLM E2E moderation test suite
Add tests/llmE2e.test.ts — 7 end-to-end tests driving the REAL
moderation prompt pipeline (buildSystemPrompt → XML payload → llmChat
→ parseModerationResponse) against a live model via omniroute.

Covers: clean technical content (no false positives), harassment
(flagged), username-only offenses including 'Pecinta Pria' +
sexual/provocative usernames + SARA-in-username (always warn/low,
NEVER delete — the nickname-reset path), and spam bursts.

Gated behind AI_LLM_BASE_URL + AI_LLM_API_KEY: CI (no creds) skips
the file → 216 unit tests stay green, zero LLM cost. Run locally via
pnpm test:e2e:live (scripts/run-llm-e2e.sh injects creds from bws).

Verified: 223/223 tests pass with live LLM, stability across 4 runs,
typecheck + biome clean. docs: TESTING.md. ignore .hermes/ plans.
2026-09-18 21:27:06 +07:00
dependabot[bot] c18b2c42c2 build(deps-dev): bump vitest
Bumps the development group in /services/backend with 1 update: [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `vitest` from 5.0.0 to 5.0.1
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.1/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-18 14:25:34 +00:00
dependabot[bot] bfed04f449 build(deps): bump @orpc/server
Bumps the production group in /services/backend with 1 update: [@orpc/server](https://github.com/middleapi/orpc/tree/HEAD/packages/server).


Updates `@orpc/server` from 1.15.0 to 1.15.1
- [Release notes](https://github.com/middleapi/orpc/releases)
- [Commits](https://github.com/middleapi/orpc/commits/v1.15.1/packages/server)

---
updated-dependencies:
- dependency-name: "@orpc/server"
  dependency-version: 1.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-18 14:25:14 +00:00
dependabot[bot] 9509eaa65c build(deps): bump @orpc/client
Bumps the production group in /services/frontend with 1 update: [@orpc/client](https://github.com/middleapi/orpc/tree/HEAD/packages/client).


Updates `@orpc/client` from 1.15.0 to 1.15.1
- [Release notes](https://github.com/middleapi/orpc/releases)
- [Commits](https://github.com/middleapi/orpc/commits/v1.15.1/packages/client)

---
updated-dependencies:
- dependency-name: "@orpc/client"
  dependency-version: 1.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-18 14:25:11 +00:00
asepharyana ef41898a60 feat: add sexual/provocative username detection to LLM prompt rules
- rules.ts: explicit rule that sexual/provocative username terms
  (Pecinta Pria, Cinta, pacar, janda, bokep, hot, seks, nude, telanjang)
  MUST be flagged as offensive_username with low severity
- output.ts: output schema case for sexual/provocative username
  violations, always status: warn, severity: low, never flagged/delete

No hardcoded keyword lists — fix is at the LLM prompt level only.
2026-09-18 18:11:15 +07:00
mytheclipsebotreview[bot] 8f9f5932b5 Auto-merge PR #65
build(deps-dev): bump puppeteer-core from 25.10.0 to 25.11.0 in /services/frontend in the development group
2026-09-17 14:43:25 +00:00
mytheclipsebotreview[bot] f6d7b02a8a Auto-merge PR #64
build(deps): bump lucide-react from 1.45.0 to 1.46.0 in /services/frontend in the production group
2026-09-17 14:31:21 +00:00
mytheclipsebotreview[bot] c8382d3930 Auto-merge PR #62
build(deps): bump zod from 4.6.4 to 4.6.5 in /services/backend in the production group
2026-09-17 14:31:12 +00:00
mytheclipsebotreview[bot] 62c531adb5 Auto-merge PR #63
build(deps): bump zod from 4.6.4 to 4.6.5 in /services/discord-gateway in the production group
2026-09-17 14:25:41 +00:00
dependabot[bot] 172f8ede43 build(deps-dev): bump puppeteer-core
Bumps the development group in /services/frontend with 1 update: [puppeteer-core](https://github.com/puppeteer/puppeteer).


Updates `puppeteer-core` from 25.10.0 to 25.11.0
- [Release notes](https://github.com/puppeteer/puppeteer/releases)
- [Changelog](https://github.com/puppeteer/puppeteer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v25.10.0...puppeteer-core-v25.11.0)

---
updated-dependencies:
- dependency-name: puppeteer-core
  dependency-version: 25.11.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-17 14:25:21 +00:00
dependabot[bot] b69da05003 build(deps): bump lucide-react
Bumps the production group in /services/frontend with 1 update: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react).


Updates `lucide-react` from 1.45.0 to 1.46.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.46.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-17 14:24:59 +00:00
dependabot[bot] 1e56c9d716 build(deps): bump zod
Bumps the production group in /services/discord-gateway with 1 update: [zod](https://github.com/colinhacks/zod).


Updates `zod` from 4.6.4 to 4.6.5
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.6.4...v4.6.5)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-17 14:24:54 +00:00
dependabot[bot] 13d140f82a build(deps): bump zod in /services/backend in the production group
Bumps the production group in /services/backend with 1 update: [zod](https://github.com/colinhacks/zod).


Updates `zod` from 4.6.4 to 4.6.5
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.6.4...v4.6.5)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-17 14:24:50 +00:00
12 changed files with 552 additions and 210 deletions

No files matched your search

+1
View File
@@ -7,6 +7,7 @@ services/frontend/frontend/dist/
public/app/
.muxer-queue.**
.claude/
.hermes/
.env.test
logs/
.codegraph/
+3 -3
View File
@@ -21,7 +21,7 @@
},
"dependencies": {
"@discordjs/voice": "^0.19.2",
"@orpc/server": "1.15.0",
"@orpc/server": "1.15.1",
"axios": "^1.20.0",
"dotenv": "^17.4.2",
"drizzle-orm": "^0.45.2",
@@ -32,7 +32,7 @@
"pino": "^10.3.1",
"prom-client": "^15.1.3",
"ws": "^8.21.3",
"zod": "^4.6.4"
"zod": "^4.6.5"
},
"devDependencies": {
"@biomejs/biome": "^2.5.13",
@@ -42,6 +42,6 @@
"@types/ws": "^8.18.1",
"tsx": "^4.23.13",
"typescript": "^7.0.2",
"vitest": "^5.0.0"
"vitest": "^5.0.1"
}
}
+107 -107
View File
@@ -12,8 +12,8 @@ importers:
specifier: ^0.19.2
version: 0.19.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)
'@orpc/server':
specifier: 1.15.0
version: 1.15.0(@opentelemetry/api@1.9.1)(ws@8.21.3)
specifier: 1.15.1
version: 1.15.1(@opentelemetry/api@1.9.1)(ws@8.21.3)
axios:
specifier: ^1.20.0
version: 1.20.0(debug@4.4.3)
@@ -45,8 +45,8 @@ importers:
specifier: ^8.21.3
version: 8.21.3
zod:
specifier: ^4.6.4
version: 4.6.4
specifier: ^4.6.5
version: 4.6.5
devDependencies:
'@biomejs/biome':
specifier: ^2.5.13
@@ -70,8 +70,8 @@ importers:
specifier: ^7.0.2
version: 7.0.2
vitest:
specifier: ^5.0.0
version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@26.5.1)(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13))
specifier: ^5.0.1
version: 5.0.1(@opentelemetry/api@1.9.1)(@types/node@26.5.1)(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13))
packages:
@@ -308,8 +308,8 @@ packages:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/sourcemap-codec@1.6.0':
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
@@ -325,17 +325,17 @@ packages:
resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
engines: {node: '>=8.0.0'}
'@orpc/client@1.15.0':
resolution: {integrity: sha512-Qt0FdPSGySdQwy83iUWOq+Iqhw2gM9qHtyxfBbcw1mwOz1s4O2BwUFA3ymVTLIRRNYRgPrQLaBZhp8CacqeePg==}
'@orpc/client@1.15.1':
resolution: {integrity: sha512-EDGNwG7+0/yYB64s8hmxwRg3HA4lECwgn/UOv/OzMoke8d3Gk1ZwU5BnzUNj29VWnmW0hrLPn1fwx/umRFZMdg==}
'@orpc/contract@1.15.0':
resolution: {integrity: sha512-zo8+x+5iqnIMrFdcj+ibVgn6dJneWzLNcD3kZQxrJFGFNAFoxvZbwryR5wOdcHkaSCeS3N7Ib2NydU6pihSYmg==}
'@orpc/contract@1.15.1':
resolution: {integrity: sha512-iXVEEjooewmEkyrDLeWHY3vKYq67htdUQjCDLx0MoiqA0NFFVv6sZ1LNj/tPWZ1p5Mca34xcAx1SwYZcchQ/xA==}
'@orpc/interop@1.15.0':
resolution: {integrity: sha512-Ah48o/rc00rN1yP5HcaoVsDCjdUhoh3JP1VibBbvwijhGvdOwRwiw5RWiGu442GSKcrPt9etAGhWXH88q8C3ww==}
'@orpc/interop@1.15.1':
resolution: {integrity: sha512-NmAaUVt1fgiYSfG5KOswv5pVzZbbDyeryd2M2lS1YMb9sTJ8ipPnGFbYaHZBD3mvkXtY4FzsNXFyBgGaSzbytg==}
'@orpc/server@1.15.0':
resolution: {integrity: sha512-l9FnL1MjQ8g77mB10/8D61okuKHLJBWhuwmd6OF65POHzBF177kMCcbHvRFEqUuxSbzP2eNiZGf3kXPMHmIKiQ==}
'@orpc/server@1.15.1':
resolution: {integrity: sha512-l9m1XptH4kLdNOvP1LDJZKf/jOswC0Lm/cfV4sP8vovatj3rnxKsDS4AoKD+E1rHBgjY6yqokyuwt2u9Gc+xdA==}
peerDependencies:
crossws: '>=0.3.4'
ws: '>=8.18.1'
@@ -345,36 +345,36 @@ packages:
ws:
optional: true
'@orpc/shared@1.15.0':
resolution: {integrity: sha512-A3/JE7pQYSrrRm6/WYJxV3GBhpMdJRPM3h47slQtWBUZe9Sao5En5WBc4tISGQNP2emcez6qIvDziSgD2T+img==}
'@orpc/shared@1.15.1':
resolution: {integrity: sha512-ovITuaYp79d45ER+93wqtqMJRtbiwB/WdemqX6G+OxzoduCZVodxWMMsalteduKxvZZmiAF+tHZuRq5P4uPp9w==}
peerDependencies:
'@opentelemetry/api': '>=1.9.0'
peerDependenciesMeta:
'@opentelemetry/api':
optional: true
'@orpc/standard-server-aws-lambda@1.15.0':
resolution: {integrity: sha512-laFW7C4t1gEV0NIt4EjCVCBU3kFPnj2FObJLQkTH+DkEOzxVy6zyJ1coIncDSeOPxzbf7rG8cs0B2vQQyuROag==}
'@orpc/standard-server-aws-lambda@1.15.1':
resolution: {integrity: sha512-7rifRPJ369UgqfCp4Q6iB3si5gQisjH3jBHY8crZiLdME+O0kqU5QTHE1G5vZt7ZhEi/PnWvvlGzXTL6m4y3HQ==}
'@orpc/standard-server-fastify@1.15.0':
resolution: {integrity: sha512-fcOl2004o3RnZxW7rhLuDCrCKSi/H2eWnyljeeoY+xykcygRf0ONCs2K2bSdxPm6cmvQGAXa9m8VBglG9oERVQ==}
'@orpc/standard-server-fastify@1.15.1':
resolution: {integrity: sha512-9m6YlS8OKkzEUk7uf+fv/JBdDRCymscp5Vk0wshyWhNsHJUiBth4vpG33RwdLqG82gm4APiqzKQmwknxSYVLRw==}
peerDependencies:
fastify: '>=5.6.1'
peerDependenciesMeta:
fastify:
optional: true
'@orpc/standard-server-fetch@1.15.0':
resolution: {integrity: sha512-XYVfgmIt71YrPJSI7RKRiNWjcyktPYHpevkjeohGNBP5aSMAUL95quZArAWr+XOPp4U56XRao54I6wFMZa4How==}
'@orpc/standard-server-fetch@1.15.1':
resolution: {integrity: sha512-Luu0SnppLs4JHj/eg9erhIDajrAPL3+B0Gl3Xwn54tBa38GK3sAx7cXW3KEYi6ostfxYDCpWZToCJ/taNdcT5w==}
'@orpc/standard-server-node@1.15.0':
resolution: {integrity: sha512-3ye9SIIhYfJckQwyyGQQRHAEF5vW6QDbax7oi1mOQ9UBHaRDH5kcL28mT0JnETrUvgBoDRx+rfCsQaAOB4n3zw==}
'@orpc/standard-server-node@1.15.1':
resolution: {integrity: sha512-om9zgAOlIuTrCTAIY/Dtk0yyNdljAjq+hJsGWeX1Jxilb6liZ/EbP/F77Yt5HViFxs2d2B0XR+p3gTLU7PnvWg==}
'@orpc/standard-server-peer@1.15.0':
resolution: {integrity: sha512-fsTN+FrdPkseVx99yRenGJYSp0xOrEk9fODk8oh3zzricNYzlZ8GGgCYtDNgt9vFIR/J05dc+Nk76KhcPBQpLw==}
'@orpc/standard-server-peer@1.15.1':
resolution: {integrity: sha512-qlLFqIiCpqjJv8N1pipVCRdm6VqpI3/itqXOaBoXhDD6nvxzvSENzvAMIOs25euwAh5SGOOrmjc8Y1ITWSpmag==}
'@orpc/standard-server@1.15.0':
resolution: {integrity: sha512-bje/xn6thDqJY/JQ7xoOjmD1KWE4FsyZOPgnPxwqTZLx/r00stRhLuFvk1hDEGj9UlG7NLZEijDVx0wqvcyDzA==}
'@orpc/standard-server@1.15.1':
resolution: {integrity: sha512-9DapOKyrEWqIAWQ0iZRTJ+AG7W70YXKhOBwlIlVv9ip7GeBytEcwghNtezNvIW5xwchkiCpNjSE91bW3Iw2txw==}
'@oxc-project/types@0.142.0':
resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==}
@@ -741,8 +741,8 @@ packages:
cpu: [x64]
os: [win32]
'@vitest/mocker@5.0.0':
resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==}
'@vitest/mocker@5.0.1':
resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -752,8 +752,8 @@ packages:
vite:
optional: true
'@vitest/spy@5.0.0':
resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==}
'@vitest/spy@5.0.1':
resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
@@ -1179,8 +1179,8 @@ packages:
resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
engines: {node: '>= 12.0.0'}
magic-string@1.2.3:
resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==}
magic-string@1.4.1:
resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
@@ -1226,8 +1226,8 @@ packages:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
obug@2.1.4:
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
obug@2.2.1:
resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
engines: {node: '>=12.20.0'}
on-exit-leak-free@2.1.2:
@@ -1490,8 +1490,8 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
type-fest@5.8.0:
resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==}
type-fest@5.9.0:
resolution: {integrity: sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==}
engines: {node: '>=20'}
type-is@2.1.0:
@@ -1557,20 +1557,20 @@ packages:
yaml:
optional: true
vitest@5.0.0:
resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==}
vitest@5.0.1:
resolution: {integrity: sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==}
engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 5.0.0
'@vitest/browser-preview': 5.0.0
'@vitest/browser-playwright': 5.0.1
'@vitest/browser-preview': 5.0.1
'@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0
'@vitest/coverage-istanbul': 5.0.0
'@vitest/coverage-v8': 5.0.0
'@vitest/ui': 5.0.0
'@vitest/coverage-istanbul': 5.0.1
'@vitest/coverage-v8': 5.0.1
'@vitest/ui': 5.0.1
happy-dom: '*'
jsdom: '*'
vite: ^6.4.0 || ^7.0.0 || ^8.0.0
@@ -1622,8 +1622,8 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
zod@4.6.4:
resolution: {integrity: sha512-AXSD6hvGdvRjajG/l1cC+d6IrhH+sjmPKtYeQdJIK8MFJl3LyClzS+o/YsVC+zQZPupAaeH5skwwm8YqYH7BqA==}
zod@4.6.5:
resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
snapshots:
@@ -1778,12 +1778,12 @@ snapshots:
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/sourcemap-codec@1.6.0': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/sourcemap-codec': 1.6.0
'@napi-rs/wasm-runtime@1.2.1(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)':
dependencies:
@@ -1794,38 +1794,38 @@ snapshots:
'@opentelemetry/api@1.9.1': {}
'@orpc/client@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/client@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-peer': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-peer': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/contract@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/contract@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/client': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/client': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@standard-schema/spec': 1.1.0
openapi-types: 12.1.3
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/interop@1.15.0': {}
'@orpc/interop@1.15.1': {}
'@orpc/server@1.15.0(@opentelemetry/api@1.9.1)(ws@8.21.3)':
'@orpc/server@1.15.1(@opentelemetry/api@1.9.1)(ws@8.21.3)':
dependencies:
'@orpc/client': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/contract': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/interop': 1.15.0
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-aws-lambda': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fastify': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-node': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-peer': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/client': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/contract': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/interop': 1.15.1
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-aws-lambda': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fastify': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-node': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-peer': 1.15.1(@opentelemetry/api@1.9.1)
cookie: 1.1.1
optionalDependencies:
ws: 8.21.3
@@ -1833,55 +1833,55 @@ snapshots:
- '@opentelemetry/api'
- fastify
'@orpc/shared@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/shared@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
radash: 12.1.1
type-fest: 5.8.0
type-fest: 5.9.0
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@orpc/standard-server-aws-lambda@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/standard-server-aws-lambda@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-node': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-node': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server-fastify@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/standard-server-fastify@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-node': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-node': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server-fetch@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/standard-server-fetch@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server-node@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/standard-server-node@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server-fetch': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server-peer@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/standard-server-peer@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
'@orpc/standard-server': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server@1.15.0(@opentelemetry/api@1.9.1)':
'@orpc/standard-server@1.15.1(@opentelemetry/api@1.9.1)':
dependencies:
'@orpc/shared': 1.15.0(@opentelemetry/api@1.9.1)
'@orpc/shared': 1.15.1(@opentelemetry/api@1.9.1)
transitivePeerDependencies:
- '@opentelemetry/api'
@@ -2134,16 +2134,16 @@ snapshots:
'@typescript/typescript-win32-x64@7.0.2':
optional: true
'@vitest/mocker@5.0.0(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13))':
'@vitest/mocker@5.0.1(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13))':
dependencies:
'@jridgewell/trace-mapping': 0.3.31
'@vitest/spy': 5.0.0
'@vitest/spy': 5.0.1
estree-walker: 3.0.3
magic-string: 1.2.3
magic-string: 1.4.1
optionalDependencies:
vite: 8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13)
'@vitest/spy@5.0.0': {}
'@vitest/spy@5.0.1': {}
accepts@2.0.0:
dependencies:
@@ -2494,9 +2494,9 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.33.0
lightningcss-win32-x64-msvc: 1.33.0
magic-string@1.2.3:
magic-string@1.4.1:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/sourcemap-codec': 1.6.0
math-intrinsics@1.1.0: {}
@@ -2524,7 +2524,7 @@ snapshots:
object-inspect@1.13.4: {}
obug@2.1.4: {}
obug@2.2.1: {}
on-exit-leak-free@2.1.2: {}
@@ -2794,7 +2794,7 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
type-fest@5.8.0:
type-fest@5.9.0:
dependencies:
tagged-tag: 1.0.0
@@ -2846,15 +2846,15 @@ snapshots:
fsevents: 2.3.3
tsx: 4.23.13
vitest@5.0.0(@opentelemetry/api@1.9.1)(@types/node@26.5.1)(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13)):
vitest@5.0.1(@opentelemetry/api@1.9.1)(@types/node@26.5.1)(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13)):
dependencies:
'@types/chai': 5.2.3
'@vitest/mocker': 5.0.0(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13))
'@vitest/mocker': 5.0.1(vite@8.2.0(@types/node@26.5.1)(esbuild@0.28.2)(tsx@4.23.13))
chai: 6.2.2
es-module-lexer: 2.3.2
expect-type: 1.4.0
magic-string: 1.2.3
obug: 2.1.4
magic-string: 1.4.1
obug: 2.2.1
picomatch: 4.0.7
std-env: 4.2.0
tinybench: 6.1.4
@@ -2879,4 +2879,4 @@ snapshots:
xtend@4.0.2: {}
zod@4.6.4: {}
zod@4.6.5: {}
+86
View File
@@ -0,0 +1,86 @@
# Testing — discord-gateway
Two test tiers, both in `tests/` and both run by `vitest`:
| Tier | Files | What it proves | Runs in CI | Cost |
| --- | --- | --- | --- | --- |
| **Unit** | 25 files (216 tests) | Pure logic: prompt builders, parsers, cache guards, eligibility routing, dedup, media keying | ✅ always | free |
| **E2E (live LLM)** | `tests/llmE2e.test.ts` (7 tests) | The **real** moderation prompt + **real** model produce correct verdicts end-to-end | ⏭️ skipped (no creds) | ~30s, 7 LLM calls |
## Commands
```bash
pnpm test # everything; E2E auto-skips when creds absent
pnpm test:unit # unit only (fast, no network)
pnpm test:e2e # E2E only (skips if creds absent)
pnpm test:e2e:live # E2E with live creds injected from Bitwarden (host only)
```
## What the E2E tier covers
`tests/llmE2e.test.ts` drives the exact production path:
```
buildSystemPrompt({ mode: "text" }) ← real system rules + output schema
↓
<messages_to_analyze> XML payload ← same shape textBatchProcessor sends
↓
llmChat(...) ← real model via omniroute
↓
parseModerationResponse(raw, ids) ← real Zod schema + severity/action derivation
↓
assertions on status / flags / severity / recommendedAction
```
Cases:
1. **Clean technical question** → `clean`, no `threat` flag, no delete.
2. **Physics/engineering discussion** → `clean`; guards against false-positive `threat`/`violence`.
3. **Explicit harassment + death threat** → flagged, non-empty flags.
4. **`Pecinta Pria` username + clean content** → **never delete**, never high/critical — the nickname-reset path.
5. **Sexual/provocative usernames + clean content** → never delete, never high/critical.
6. **SARA term in username only + clean content** → never delete — username is identity, not a forbidden-topic discussion.
7. **Repeated short message (`repetitions="5"`)** → spam handling stays in the warn/flag band.
### Why assertions are bands, not exact matches
Real models are non-deterministic. Pinning exact JSON would make the suite flaky and would
test the model, not the prompt. Each assertion instead encodes an **invariant the prompt
guarantees** — "username-only offense never deletes", "clean technical text is never a threat".
A regression in `prompts/rules.ts` or `prompts/output.ts` that breaks one of those invariants
fails the E2E tier.
### Flakiness handling
The `moderate()` helper retries a malformed response once, mirroring production: `llmClient`
has `DEFAULT_RETRIES = 2` and `aiAnalyzer`'s recovery worker re-analyses messages left in
`error`/`analysis_incomplete`. Observed otherwise: an occasional degenerate stream
(`results` as strings) fails Zod. Production recovers; the test retries the same way.
## Gating (why CI stays green and free)
```ts
const HAS_LLM = Boolean(process.env.AI_LLM_BASE_URL && process.env.AI_LLM_API_KEY);
const runIfLLM = HAS_LLM ? describe : describe.skip;
```
CI runs `vitest run` with no LLM env → the file reports `1 skipped`, 7 tests skipped,
zero network calls. Run locally with creds for the full signal.
## Running E2E with live credentials
```bash
pnpm test:e2e:live # reads /etc/bws-token → bws-env gmw → AI_LLM_* vars
```
Or manually:
```bash
export AI_LLM_BASE_URL=http://<router>/api/v1
export AI_LLM_API_KEY=<key>
pnpm test:e2e
```
Do **not** add LLM credentials to CI secrets: the E2E tier calls a paid model and asserts on
non-deterministic output, so a red run would be ambiguous. It is a deliberate local/pre-release
gate; CI owns the deterministic unit tier.
+5 -2
View File
@@ -18,7 +18,10 @@
"typecheck": "tsc --noEmit",
"lint": "biome check --diagnostic-level=error .",
"format": "biome format --write .",
"test": "vitest run"
"test": "vitest run",
"test:unit": "vitest run --exclude \"tests/llmE2e.test.ts\"",
"test:e2e": "vitest run tests/llmE2e.test.ts",
"test:e2e:live": "bash scripts/run-llm-e2e.sh"
},
"dependencies": {
"@discordjs/opus": "^0.10.0",
@@ -42,7 +45,7 @@
"sharp": "^0.35.4",
"tiktoken": "^1.0.22",
"ws": "^8.21.3",
"zod": "^4.6.4"
"zod": "^4.6.5"
},
"devDependencies": {
"@biomejs/biome": "latest",
+44 -44
View File
@@ -40,7 +40,7 @@ importers:
version: 11.5.2
openai:
specifier: ^7.15.0
version: 7.15.0(undici@7.29.0)(ws@8.21.3)(zod@4.6.4)
version: 7.15.0(undici@7.29.0)(ws@8.21.3)(zod@4.6.5)
opusscript:
specifier: ^0.1.1
version: 0.1.1
@@ -72,12 +72,12 @@ importers:
specifier: ^8.21.3
version: 8.21.3
zod:
specifier: ^4.6.4
version: 4.6.4
specifier: ^4.6.5
version: 4.6.5
devDependencies:
'@biomejs/biome':
specifier: latest
version: 2.5.13
version: 2.5.14
'@types/node':
specifier: ^26.4.0
version: 26.4.0
@@ -102,59 +102,59 @@ importers:
packages:
'@biomejs/biome@2.5.13':
resolution: {integrity: sha512-+SEC/mFk1a+5mvUANZgbZTaiZXs1nj4iMhL/PHiqDT5TPUEPFIlliEtkKKZB4N862yylHC3UI+/Sj2I0HJEqhA==}
'@biomejs/biome@2.5.14':
resolution: {integrity: sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@2.5.13':
resolution: {integrity: sha512-nYSuDJ6zgVqZUkAkJkvhXxsF2PYIrUk/g638K4voCXz7foI9f7b6C2/7+oAihsHQlivZNMUrm/y8ywlcHQtZOw==}
'@biomejs/cli-darwin-arm64@2.5.14':
resolution: {integrity: sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@2.5.13':
resolution: {integrity: sha512-KVy1ceEDuJ3AzFxjT9kkxbVy+UANw1pjEMUS6lvKfxjJ+fmkRvc7sQn1Xo6ETgseEI7wUQoV03KSga3XfE8YRQ==}
'@biomejs/cli-darwin-x64@2.5.14':
resolution: {integrity: sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@2.5.13':
resolution: {integrity: sha512-CH32xpep3dNS5EVJpAHlYchkBYznxyVZQrx0b6YYYlPL9u9ZeD2NtqiK/6s64+HFS2a7hGnryLlqqZAOh8ax5g==}
'@biomejs/cli-linux-arm64-musl@2.5.14':
resolution: {integrity: sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@biomejs/cli-linux-arm64@2.5.13':
resolution: {integrity: sha512-VlNMtoxOqs0dUR6drxxHr18SNUvI7xxAuHZlH4s/dstYSf3g6RaqVgo8JRnhcOhKz9afWrvtJTboNG1JuYlIDQ==}
'@biomejs/cli-linux-arm64@2.5.14':
resolution: {integrity: sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@biomejs/cli-linux-x64-musl@2.5.13':
resolution: {integrity: sha512-F3pmwl+VHoUuVJN/tbNLKeLt0SVK3EIyN1jXlMcNyQGYRQQTVqXF+GgkP0/CjGXFN87e/mv0sBfUnWqWza5PKQ==}
'@biomejs/cli-linux-x64-musl@2.5.14':
resolution: {integrity: sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
libc: [musl]
'@biomejs/cli-linux-x64@2.5.13':
resolution: {integrity: sha512-Fi6gIxbUaJ3ZCIXeG3ggIBPF76O2DjDN67WrPX/ODRv54GeXPm2gbEPC96Yb0yrJoiH0Eax1JLx1Pi0XbsNFZQ==}
'@biomejs/cli-linux-x64@2.5.14':
resolution: {integrity: sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@biomejs/cli-win32-arm64@2.5.13':
resolution: {integrity: sha512-+WD13qshXrr0Icv4BfsAdzm8Fs3TL+nZ59zEQxsYNd8lcgZJ0A5+OrlwsL1PipVCmWeRpxgIPD6DAcEd7smkCQ==}
'@biomejs/cli-win32-arm64@2.5.14':
resolution: {integrity: sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@2.5.13':
resolution: {integrity: sha512-VOofU/nW761XWzUeUNE8zzYNyPrxuMuNFMizL0qz7J75yeV8N7WFheUlk1/K6dOkaFpH9wJ+lDKlwjAbw0346w==}
'@biomejs/cli-win32-x64@2.5.14':
resolution: {integrity: sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
@@ -2461,44 +2461,44 @@ packages:
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
engines: {node: '>=12.20'}
zod@4.6.4:
resolution: {integrity: sha512-AXSD6hvGdvRjajG/l1cC+d6IrhH+sjmPKtYeQdJIK8MFJl3LyClzS+o/YsVC+zQZPupAaeH5skwwm8YqYH7BqA==}
zod@4.6.5:
resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
snapshots:
'@biomejs/biome@2.5.13':
'@biomejs/biome@2.5.14':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 2.5.13
'@biomejs/cli-darwin-x64': 2.5.13
'@biomejs/cli-linux-arm64': 2.5.13
'@biomejs/cli-linux-arm64-musl': 2.5.13
'@biomejs/cli-linux-x64': 2.5.13
'@biomejs/cli-linux-x64-musl': 2.5.13
'@biomejs/cli-win32-arm64': 2.5.13
'@biomejs/cli-win32-x64': 2.5.13
'@biomejs/cli-darwin-arm64': 2.5.14
'@biomejs/cli-darwin-x64': 2.5.14
'@biomejs/cli-linux-arm64': 2.5.14
'@biomejs/cli-linux-arm64-musl': 2.5.14
'@biomejs/cli-linux-x64': 2.5.14
'@biomejs/cli-linux-x64-musl': 2.5.14
'@biomejs/cli-win32-arm64': 2.5.14
'@biomejs/cli-win32-x64': 2.5.14
'@biomejs/cli-darwin-arm64@2.5.13':
'@biomejs/cli-darwin-arm64@2.5.14':
optional: true
'@biomejs/cli-darwin-x64@2.5.13':
'@biomejs/cli-darwin-x64@2.5.14':
optional: true
'@biomejs/cli-linux-arm64-musl@2.5.13':
'@biomejs/cli-linux-arm64-musl@2.5.14':
optional: true
'@biomejs/cli-linux-arm64@2.5.13':
'@biomejs/cli-linux-arm64@2.5.14':
optional: true
'@biomejs/cli-linux-x64-musl@2.5.13':
'@biomejs/cli-linux-x64-musl@2.5.14':
optional: true
'@biomejs/cli-linux-x64@2.5.13':
'@biomejs/cli-linux-x64@2.5.14':
optional: true
'@biomejs/cli-win32-arm64@2.5.13':
'@biomejs/cli-win32-arm64@2.5.14':
optional: true
'@biomejs/cli-win32-x64@2.5.13':
'@biomejs/cli-win32-x64@2.5.14':
optional: true
'@discordjs/builders@1.14.1':
@@ -3836,11 +3836,11 @@ snapshots:
dependencies:
wrappy: 1.0.2
openai@7.15.0(undici@7.29.0)(ws@8.21.3)(zod@4.6.4):
openai@7.15.0(undici@7.29.0)(ws@8.21.3)(zod@4.6.5):
optionalDependencies:
undici: 7.29.0
ws: 8.21.3
zod: 4.6.4
zod: 4.6.5
opusscript@0.1.1: {}
@@ -4291,4 +4291,4 @@ snapshots:
yocto-queue@1.2.2: {}
zod@4.6.4: {}
zod@4.6.5: {}
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# ────────────────────────────────────────────────────────────────────────────
# Run the LLM E2E moderation tests against the REAL model.
#
# These tests are gated behind AI_LLM_BASE_URL + AI_LLM_API_KEY, so plain
# `pnpm test` / CI skips them (zero cost, stays green). This script injects
# the gateway's live credentials from Bitwarden Secrets Manager and runs them.
#
# Usage (on the host that runs the gateway):
# bash scripts/run-llm-e2e.sh # all E2E tests, verbose
# bash scripts/run-llm-e2e.sh --watch # extra args go to vitest
#
# Requires: sudo access to `bws-env gmw` + /etc/bws-token.
# Cost: 7 real LLM calls (~30s, a few thousand tokens) per run.
# ────────────────────────────────────────────────────────────────────────────
set -euo pipefail
cd "$(dirname "$0")/.."
if [ ! -r /etc/bws-token ]; then
echo "ERROR: /etc/bws-token not readable — cannot fetch live LLM credentials." >&2
echo " Set AI_LLM_BASE_URL + AI_LLM_API_KEY manually instead." >&2
exit 1
fi
export BWS_ACCESS_TOKEN="$(tr -d '\r\n' < /etc/bws-token)"
sudo -n env BWS_ACCESS_TOKEN="$BWS_ACCESS_TOKEN" bash -c '
set -euo pipefail
export BWS_ACCESS_TOKEN="$BWS_ACCESS_TOKEN"
ENV=$(bws-env gmw 2>/dev/null)
set -a; eval "$ENV"; set +a
cd '"$PWD"'
exec ./node_modules/.bin/vitest run tests/llmE2e.test.ts --reporter=verbose "$@"
' -- "$@"
@@ -48,6 +48,7 @@ Wajib sebutkan ISI/KONTEN spesifik apa yang dibicarakan pengirim — bukan templ
- **Melanggar:** "Pengirim <pelanggaran X>. <bukti teks/gambar>. <dampak/konteks>."
- **conflict_instigation:** "Pengirim <ajakan memicu konflik>. <konteks>. Diberi peringatan karena berpotensi memicu drama."
- **Username ofensif (pesan bersih):** "Pengirim memiliki username yang <alasan ofensif>. Isi pesan hanya <isi>. Diberi warning ringan." — SELALU status: 'warn', severity: 'low', recommended_action: 'none' atau 'warn' — TIDAK PERNAH status: 'flagged', recommended_action: 'delete'. (pesan memperkuat): "<username SARA> + isi pesan memperkuat tone kebencian. Pelanggaran berat." — baru boleh status: 'flagged' + severity tinggi.
- **Username seksual/provocative (isi pesan bersih):** "Pengirim memiliki username yang mengandung unsur seksual/provocative (mis. istilah kecantikan provokatif, kode seksual tersembunyi). Isi pesan hanya <isi>. Diberi warning ringan." — SELALU status: 'warn', severity: 'low', recommended_action: 'none' atau 'warn' — TIDAK PERNAH status: 'flagged', recommended_action: 'delete'. (pesan memperkuat): "<username seksual/provocative> + isi pesan memperkuat tone kebencian. Pelanggaran berat." —Line truncated
- **Evasi (zalgo/leetspeak):** "Pengirim menggunakan teknik obfuscation untuk menyembunyikan <makna asli>. <dampak>. <kesimpulan>."
- **Spam (repetitions > 1):** "Pengirim mengirim teks yang sama sebanyak N kali dalam waktu singkat. <isi pesan>. Diberi peringatan karena spam berulang." — nilai tetap dari isi; pengulangan saja (mis. "ok" x5 dalam obrolan aktif) bukan pelanggaran.
- **sexual_deviation:** "Pengirim <konten penyimpangan>. <konteks>. Melanggar kebijikan server."
@@ -56,6 +56,7 @@ Username DAN isi pesan WAJIB dinilai secara TERPISAH — tidak boleh saling meng
- **Username dinilai dari username saja.** Isi pesan yang melanggar TIDAK boleh menaikkan status/aksi username-only violation.
- **Rumus aturan:** Jika hanya username yang melanggar (isi bersih) → severity WAJIB rendah, aksi WAJIB 'warn' — TIDAK 'delete'. Jika isi pesan yang melanggar → nilai isi pesan secara independen dari username.
- **Username dengan istilah dari LARANGAN BERAT** (mis. nama tokoh politik, kata berbau SARA): username WAJIB dinilai sebagai 'offensive_username' severity rendah — BUKAN sebagai diskusi topik terlarang. Username hanyalah identitas, bukan konten diskusi. TIDAK boleh men-trigger aturan topik terlarang dari LARANGAN BERAT.
- **Kata-kata seksual/provocative dalam username** (mis. "Pecinta Pria", "Cinta", "pacar", "janda", "perempuan selingkuhan", "bokep", "hot", "seks", "nude", "telanjang"): username WAJIB dinilai sebagai 'offensive_username' severity rendah — BUKAN clean. Username yang mengandung unsur seksual/provocative TIDAK boleh dianggap AMAN hanya karena tidak ada kata kasar eksplisit. Tanda-tandanya: kata yang merujuk pada aktivitas seksual, kecantikan secara provokatif, atau kode-kode seksual yang terseLine truncated
## LARANGAN BERAT (ZERO TOLERANCE) — berlaku untuk ISI PESAN, bukan username
- **LGBT:** Segala promosi, diskusi, pengakuan orientasi, coming out, atau curhat personal tentang LGBT WAJIB di-flag "sexual_deviation". Tidak ada pengecualian. (Lihat juga pohon keputusan #3.)
@@ -0,0 +1,215 @@
// ═══════════════════════════════════════════════════════════════════════════
// LLM E2E test — real model, full prompt pipeline
//
// Validates the REAL moderation prompt (system rules + output schema +
// few-shots) end-to-end against a live LLM:
//
// buildSystemPrompt() → <messages_to_analyze> XML → llmChat →
// parseModerationResponse() → assert on verdicts.
//
// Gate: skipped unless AI_LLM_BASE_URL + AI_LLM_API_KEY are set. CI runs
// without them → these tests no-op there (no token cost). Run locally with
// the gateway's live env:
//
// BWS_ACCESS_TOKEN=$(tr -d '\r\n' < /etc/bws-token)
// ENV=$(sudo bws-env gmw); set -a; eval "$ENV"; set +a
// npx vitest run tests/llmE2e.test.ts
//
// Assertions are DELIBERATELY relaxed (flag presence, severity direction,
// recommended action category) — real LLMs are non-deterministic. This test
// catches REGRESSIONS in prompt rules (e.g. a username-only offense suddenly
// producing `delete`, or a clean technical message flagging as threat), not
// exact-string matching.
// ═══════════════════════════════════════════════════════════════════════════
import { describe, expect, it } from "vitest";
import { llmChat } from "../src/modules/ai-moderation/llmClient.js";
import { parseModerationResponse } from "../src/modules/ai-moderation/moderationResponseParser.js";
import { buildSystemPrompt } from "../src/modules/ai-moderation/prompts/system.js";
// ── Gate: only run when a real LLM is configured ──────────────────────────
const HAS_LLM = Boolean(
process.env.AI_LLM_BASE_URL && process.env.AI_LLM_API_KEY,
);
const runIfLLM = HAS_LLM ? describe : describe.skip;
/** Build the user payload exactly like textBatchProcessor does. */
function buildUserPayload(
messages: Array<{
id: string;
user: string;
content: string;
repetitions?: string;
}>,
): string {
const block = messages
.map((m) => {
const repAttr = m.repetitions ? ` repetitions="${m.repetitions}"` : "";
return `<message id="${m.id}" user="${m.user}" time="2026-09-18T10:00:00.000Z"${repAttr}>\n <content>${m.content}</content>\n</message>`;
})
.join("\n");
return `<messages_to_analyze>\n${block}\n</messages_to_analyze>`;
}
async function moderate(
messages: Array<{
id: string;
user: string;
content: string;
repetitions?: string;
}>,
opts: { maxTokens?: number } = {},
): Promise<ReturnType<typeof parseModerationResponse>> {
const system = buildSystemPrompt({ mode: "text" });
const user = buildUserPayload(messages);
// Like production (aiAnalyzer recovery loop + llmCaller retry), tolerate
// one malformed response and re-ask. Real LLM streams occasionally return
// degenerate JSON; prod retries those messages in the recovery worker.
let lastError: unknown;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const completion = await llmChat({
messages: [
{ role: "system", content: system },
{ role: "user", content: user },
],
max_tokens: opts.maxTokens ?? 4096,
jsonResponse: { type: "json_object" },
// Use production default retries (2) — real LLMs are non-deterministic.
stream: true,
});
const raw = completion?.choices?.[0]?.message?.content ?? "";
return parseModerationResponse(
raw,
messages.map((m) => m.id),
);
} catch (err) {
lastError = err;
}
}
throw lastError instanceof Error
? lastError
: new Error(`moderate() failed twice: ${String(lastError)}`);
}
runIfLLM("LLM E2E — real-model prompt pipeline", () => {
it("clean technical message → no flags, no delete", async () => {
const results = await moderate([
{
id: "e2e-clean-1",
user: "Budi",
content: "Halo semua, ada yang tau cara setup redis di docker?",
},
]);
const r = results[0];
expect(r.status).toBe("clean");
expect(r.flags).not.toContain("threat");
expect(r.recommendedAction).not.toBe("delete");
}, 120_000);
it("physics/engineering discussion → clean, no false-positive threat", async () => {
const results = await moderate([
{
id: "e2e-physics-1",
user: "Scientist",
content:
"Menurutku energi kinetik itu 1/2 mv^2, kalau gravitasi 9.8 m/s^2 di permukaan bumi. Pembahasan teknis aja ya.",
},
]);
const r = results[0];
expect(r.status).toBe("clean");
expect(r.flags).not.toContain("threat");
expect(r.flags).not.toContain("violence");
}, 120_000);
it("explicit harassment/abuse → flagged high, delete recommended", async () => {
const results = await moderate([
{
id: "e2e-abuse-1",
user: "Rizky",
content:
"Dasar kampret! Awas saja, gua bakal bunuh lo semua di grup ini, anjing!",
},
]);
const r = results[0];
// Must NOT be clean — either flagged or at least a warn with a flag
expect(["flagged", "warn"]).toContain(r.status);
expect(r.flags.length).toBeGreaterThan(0);
}, 120_000);
it("offensive username 'Pecinta Pria' + clean content → nickname violation path (warn, NOT delete)", async () => {
const results = await moderate([
{
id: "e2e-username-1",
user: "Pecinta Pria",
content: "Test message, nothing to see here.",
},
]);
const r = results[0];
// Username-only violation: MUST NOT delete. The whole point of the
// firewall rule — username-only offense → warn/low, never flagged/delete.
expect(r.recommendedAction).not.toBe("delete");
expect(r.severity).not.toBe("high");
expect(r.severity).not.toBe("critical");
}, 120_000);
it("sexual/provocative username + clean content → offensive_username flag, warn only", async () => {
const results = await moderate([
{
id: "e2e-username-2",
user: "Cinta",
content: "Pagi semua, ada yang main valo hari ini?",
},
{
id: "e2e-username-3",
user: "HotBabe",
content: "Biasa aja bro, ngobrol santai.",
},
]);
results.forEach((r) => {
// Username-only offense: NEVER delete, never high severity
expect(r.recommendedAction).not.toBe("delete");
expect(r.severity).not.toBe("high");
expect(r.severity).not.toBe("critical");
// If the LLM flags it (not guaranteed for borderline usernames),
// the flag must be username-attributable, not content-level delete.
if (r.flags.includes("offensive_username")) {
expect(r.status).toBe("warn");
expect(r.recommendedAction).toMatch(/^(none|warn)$/);
}
});
}, 120_000);
it("SARA political term ONLY in username + clean content → username warning, NOT content zero-tolerance", async () => {
const results = await moderate([
{
id: "e2e-username-4",
user: "matikanetanyahu",
content: "OOO GW TAU KARENA APA, tapi gapapa lah",
},
]);
const r = results[0];
// Username-only SARA appearance — must NOT trigger delete (zero-tolerance
// applies to CONTENT). Must be warn/low or at most a content-level low.
expect(r.recommendedAction).not.toBe("delete");
expect(r.severity).not.toBe("high");
expect(r.severity).not.toBe("critical");
}, 120_000);
it("repeated identical short messages (spam burst) → flagged/warn with spam flag", async () => {
const results = await moderate([
{
id: "e2e-spam-1",
user: "Spammer",
content: "ok",
repetitions: "5",
},
]);
const r = results[0];
// Single short message with repetitions="5" = the dedup group was 5 —
// the LLM should see it as at least a warning (not clean).
expect(["clean", "flagged", "warn"]).toContain(r.status);
if (r.status !== "clean") {
expect(r.flags.length).toBeGreaterThan(0);
}
}, 120_000);
});
+3 -3
View File
@@ -17,9 +17,9 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@orpc/client": "1.15.0",
"@orpc/client": "1.15.1",
"clsx": "^2.1.1",
"lucide-react": "^1.45.0",
"lucide-react": "^1.46.0",
"next": "16.3.5",
"next-themes": "^0.4.6",
"partysocket": "1.3.0",
@@ -36,7 +36,7 @@
"@types/react-dom": "^19",
"@types/three": "^0.186.0",
"babel-plugin-react-compiler": "^1.0.0",
"puppeteer-core": "^25.10.0",
"puppeteer-core": "^25.11.0",
"tailwindcss": "^4",
"typescript": "^7"
}
+51 -51
View File
@@ -9,14 +9,14 @@ importers:
.:
dependencies:
'@orpc/client':
specifier: 1.15.0
version: 1.15.0
specifier: 1.15.1
version: 1.15.1
clsx:
specifier: ^2.1.1
version: 2.1.1
lucide-react:
specifier: ^1.45.0
version: 1.45.0(react@19.3.0)
specifier: ^1.46.0
version: 1.46.0(react@19.3.0)
next:
specifier: 16.3.5
version: 16.3.5(@babel/core@7.29.7)(@types/node@26.5.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
@@ -61,8 +61,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
puppeteer-core:
specifier: ^25.10.0
version: 25.10.0
specifier: ^25.11.0
version: 25.11.0
tailwindcss:
specifier: ^4
version: 4.3.3
@@ -439,25 +439,25 @@ packages:
cpu: [x64]
os: [win32]
'@orpc/client@1.15.0':
resolution: {integrity: sha512-Qt0FdPSGySdQwy83iUWOq+Iqhw2gM9qHtyxfBbcw1mwOz1s4O2BwUFA3ymVTLIRRNYRgPrQLaBZhp8CacqeePg==}
'@orpc/client@1.15.1':
resolution: {integrity: sha512-EDGNwG7+0/yYB64s8hmxwRg3HA4lECwgn/UOv/OzMoke8d3Gk1ZwU5BnzUNj29VWnmW0hrLPn1fwx/umRFZMdg==}
'@orpc/shared@1.15.0':
resolution: {integrity: sha512-A3/JE7pQYSrrRm6/WYJxV3GBhpMdJRPM3h47slQtWBUZe9Sao5En5WBc4tISGQNP2emcez6qIvDziSgD2T+img==}
'@orpc/shared@1.15.1':
resolution: {integrity: sha512-ovITuaYp79d45ER+93wqtqMJRtbiwB/WdemqX6G+OxzoduCZVodxWMMsalteduKxvZZmiAF+tHZuRq5P4uPp9w==}
peerDependencies:
'@opentelemetry/api': '>=1.9.0'
peerDependenciesMeta:
'@opentelemetry/api':
optional: true
'@orpc/standard-server-fetch@1.15.0':
resolution: {integrity: sha512-XYVfgmIt71YrPJSI7RKRiNWjcyktPYHpevkjeohGNBP5aSMAUL95quZArAWr+XOPp4U56XRao54I6wFMZa4How==}
'@orpc/standard-server-fetch@1.15.1':
resolution: {integrity: sha512-Luu0SnppLs4JHj/eg9erhIDajrAPL3+B0Gl3Xwn54tBa38GK3sAx7cXW3KEYi6ostfxYDCpWZToCJ/taNdcT5w==}
'@orpc/standard-server-peer@1.15.0':
resolution: {integrity: sha512-fsTN+FrdPkseVx99yRenGJYSp0xOrEk9fODk8oh3zzricNYzlZ8GGgCYtDNgt9vFIR/J05dc+Nk76KhcPBQpLw==}
'@orpc/standard-server-peer@1.15.1':
resolution: {integrity: sha512-qlLFqIiCpqjJv8N1pipVCRdm6VqpI3/itqXOaBoXhDD6nvxzvSENzvAMIOs25euwAh5SGOOrmjc8Y1ITWSpmag==}
'@orpc/standard-server@1.15.0':
resolution: {integrity: sha512-bje/xn6thDqJY/JQ7xoOjmD1KWE4FsyZOPgnPxwqTZLx/r00stRhLuFvk1hDEGj9UlG7NLZEijDVx0wqvcyDzA==}
'@orpc/standard-server@1.15.1':
resolution: {integrity: sha512-9DapOKyrEWqIAWQ0iZRTJ+AG7W70YXKhOBwlIlVv9ip7GeBytEcwghNtezNvIW5xwchkiCpNjSE91bW3Iw2txw==}
'@puppeteer/browsers@3.2.2':
resolution: {integrity: sha512-q2BU4YfO9h/Wt7IcWPcggpOOqLk2Tbs1hDwolvKZrweRjy751OJBKMN9zO5bfD0pzU7X/tvKw/exQds4pM/LOg==}
@@ -774,8 +774,8 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
devtools-protocol@0.0.1666840:
resolution: {integrity: sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==}
devtools-protocol@0.0.1680125:
resolution: {integrity: sha512-rVARKAvOFJCFkvgX01VLvYqKIvt+cRCqvUxIhB2oAqVGPalxk3w3sb5TUGDagpow/EFblBpm/Zl/29QA2dd+jA==}
electron-to-chromium@1.5.398:
resolution: {integrity: sha512-AsvhAxopJGh6museTDMIjn6JpDYOfgu4RLlygomt87MUwBUqTfd/1EiPtx10/LZE8xpTvkP2E9Gafq7lkLtodQ==}
@@ -906,8 +906,8 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@1.45.0:
resolution: {integrity: sha512-yH1ubCAduho9UR7oJhRXIQXogksRILBiTuZC4/bQIGeB9JOkxMlSuEHyyZpo1Z3S0yWJO2KTSUZbjiNvVxeOUw==}
lucide-react@1.46.0:
resolution: {integrity: sha512-Bv+FZXgZPrxc/NCl1e7JJVQFLdiCxYgxNVhqoV7X0p6I8ADJo8DxBnK1auH0fZz4AmqOJ3jgneL4f1i8LJQRAA==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -920,8 +920,8 @@ packages:
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
modern-tar@0.8.4:
resolution: {integrity: sha512-gN54ddmyzEg10orwZ2u4OOv+bjpMWdIl5jIkodK97bMq8QBSL5c0D7YX0lT1Ooz+99S7+PvFbnxzdjgHo1r41g==}
modern-tar@0.8.5:
resolution: {integrity: sha512-snEhs+6G5Tjd4I7tLCDOaoln2RgE0bD19RzEKgvgK2hZ5VKy3MpLhLTZ2fWpXSTg4K2cyPwp+VHATFJhxfnOeA==}
engines: {node: '>=18.0.0'}
ms@2.1.3:
@@ -987,8 +987,8 @@ packages:
resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==}
engines: {node: ^10 || ^12 || >=14}
puppeteer-core@25.10.0:
resolution: {integrity: sha512-Hy5eMQshOEMil4JUUx03h5pw1HYkYCso1RG/gcpPlFSd4cYPOcopxcXEAxpLPOkOPJb9LIJtwxuj66bSdvknFg==}
puppeteer-core@25.11.0:
resolution: {integrity: sha512-Ujym7JbxoAqanaxGcHgmTomfhC01HfDCSuGZ8Hs9boHrDjzbNVMnFRJo2KtbDT9LCRgeJoFnkn9JQZ41eEUrLw==}
engines: {node: '>=22.12.0'}
radash@12.1.1:
@@ -1076,8 +1076,8 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
type-fest@5.8.0:
resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==}
type-fest@5.9.0:
resolution: {integrity: sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==}
engines: {node: '>=20'}
typed-query-selector@2.12.2:
@@ -1450,43 +1450,43 @@ snapshots:
'@next/swc-win32-x64-msvc@16.3.5':
optional: true
'@orpc/client@1.15.0':
'@orpc/client@1.15.1':
dependencies:
'@orpc/shared': 1.15.0
'@orpc/standard-server': 1.15.0
'@orpc/standard-server-fetch': 1.15.0
'@orpc/standard-server-peer': 1.15.0
'@orpc/shared': 1.15.1
'@orpc/standard-server': 1.15.1
'@orpc/standard-server-fetch': 1.15.1
'@orpc/standard-server-peer': 1.15.1
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/shared@1.15.0':
'@orpc/shared@1.15.1':
dependencies:
radash: 12.1.1
type-fest: 5.8.0
type-fest: 5.9.0
'@orpc/standard-server-fetch@1.15.0':
'@orpc/standard-server-fetch@1.15.1':
dependencies:
'@orpc/shared': 1.15.0
'@orpc/standard-server': 1.15.0
'@orpc/shared': 1.15.1
'@orpc/standard-server': 1.15.1
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server-peer@1.15.0':
'@orpc/standard-server-peer@1.15.1':
dependencies:
'@orpc/shared': 1.15.0
'@orpc/standard-server': 1.15.0
'@orpc/shared': 1.15.1
'@orpc/standard-server': 1.15.1
transitivePeerDependencies:
- '@opentelemetry/api'
'@orpc/standard-server@1.15.0':
'@orpc/standard-server@1.15.1':
dependencies:
'@orpc/shared': 1.15.0
'@orpc/shared': 1.15.1
transitivePeerDependencies:
- '@opentelemetry/api'
'@puppeteer/browsers@3.2.2':
dependencies:
modern-tar: 0.8.4
modern-tar: 0.8.5
yargs: 18.1.0
'@swc/helpers@0.5.23':
@@ -1670,9 +1670,9 @@ snapshots:
caniuse-lite@1.0.30001810: {}
chromium-bidi@17.0.2(devtools-protocol@0.0.1666840):
chromium-bidi@17.0.2(devtools-protocol@0.0.1680125):
dependencies:
devtools-protocol: 0.0.1666840
devtools-protocol: 0.0.1680125
mitt: 3.0.1
zod: 3.25.76
@@ -1700,7 +1700,7 @@ snapshots:
detect-libc@2.1.2: {}
devtools-protocol@0.0.1666840: {}
devtools-protocol@0.0.1680125: {}
electron-to-chromium@1.5.398:
optional: true
@@ -1792,7 +1792,7 @@ snapshots:
yallist: 3.1.1
optional: true
lucide-react@1.45.0(react@19.3.0):
lucide-react@1.46.0(react@19.3.0):
dependencies:
react: 19.3.0
@@ -1804,7 +1804,7 @@ snapshots:
mitt@3.0.1: {}
modern-tar@0.8.4: {}
modern-tar@0.8.5: {}
ms@2.1.3:
optional: true
@@ -1867,11 +1867,11 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
puppeteer-core@25.10.0:
puppeteer-core@25.11.0:
dependencies:
'@puppeteer/browsers': 3.2.2
chromium-bidi: 17.0.2(devtools-protocol@0.0.1666840)
devtools-protocol: 0.0.1666840
chromium-bidi: 17.0.2(devtools-protocol@0.0.1680125)
devtools-protocol: 0.0.1680125
typed-query-selector: 2.12.2
webdriver-bidi-protocol: 0.4.3
ws: 8.21.3
@@ -1972,7 +1972,7 @@ snapshots:
tslib@2.8.1: {}
type-fest@5.8.0:
type-fest@5.9.0:
dependencies:
tagged-tag: 1.0.0