Commit Graph
482 Commits
Author SHA1 Message Date
dependabot[bot] 08e5a161f2 build(deps): bump the production group
Bumps the production group in /services/discord-gateway with 3 updates: [lru-cache](https://github.com/isaacs/node-lru-cache), [openai](https://github.com/openai/openai-node) and [p-limit](https://github.com/sindresorhus/p-limit).


Updates `lru-cache` from 11.5.2 to 11.5.3
- [Changelog](https://github.com/isaacs/node-lru-cache/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-lru-cache/compare/v11.5.2...v11.5.3)

Updates `openai` from 7.18.0 to 7.19.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-node/compare/v7.18.0...v7.19.0)

Updates `p-limit` from 7.3.2 to 7.3.3
- [Release notes](https://github.com/sindresorhus/p-limit/releases)
- [Commits](https://github.com/sindresorhus/p-limit/compare/v7.3.2...v7.3.3)

---
updated-dependencies:
- dependency-name: lru-cache
  dependency-version: 11.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: openai
  dependency-version: 7.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: p-limit
  dependency-version: 7.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-22 14:25:31 +00:00
mytheclipsebotreview[bot] 22d896c7eb Auto-merge PR #71
build(deps): bump dotenv from 17.4.2 to 18.0.0 in /services/discord-gateway
2026-09-21 14:51:56 +00:00
dependabot[bot] f0f5100253 build(deps): bump dotenv in /services/discord-gateway
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.4.2 to 18.0.0.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.4.2...v18.0.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 18.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-21 14:26:22 +00:00
dependabot[bot] 09e4f9aa84 build(deps): bump openai
Bumps the production group in /services/discord-gateway with 1 update: [openai](https://github.com/openai/openai-node).


Updates `openai` from 7.15.0 to 7.18.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-node/compare/v7.15.0...v7.18.0)

---
updated-dependencies:
- dependency-name: openai
  dependency-version: 7.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-21 14:26:05 +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
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
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] 4c0c76089d 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.2 to 4.6.4
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.6.2...v4.6.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 14:24:57 +00:00
dependabot[bot] dc4347ef9f build(deps): bump the production group
Bumps the production group in /services/discord-gateway with 2 updates: [openai](https://github.com/openai/openai-node) and [zod](https://github.com/colinhacks/zod).


Updates `openai` from 7.10.0 to 7.15.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-node/compare/v7.10.0...v7.15.0)

Updates `zod` from 4.5.4 to 4.6.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.5.4...v4.6.2)

---
updated-dependencies:
- dependency-name: openai
  dependency-version: 7.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: zod
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-14 14:26:29 +00:00
asepharyana 5c133f7302 feat(gateway): tinyfish web search as fallback when wikipedia misses
- new tinyFishSearch module: GET api.search.tinyfish.ai with X-API-Key,
  maps top-3 to SearchResult shape, never throws (all failure modes -> [])
- wikipediaSearch: on wiki miss, one tinyfish attempt; hits cached 6h
  under the same key so fallback latency is paid once
- termGlossary: on summary miss, top tinyfish hit becomes the definition
  (persisted permanently like wiki defs); miss keeps 1h sentinel
- config: TINYFISH_API_KEY (empty = fallback disabled), ENABLED,
  BASE_URL, TIMEOUT_MS, LOCATION, LANGUAGE knobs
- tests: 6 coverage for disabled/mapping/non-OK/network/bad-json

API key NOT committed — set TINYFISH_API_KEY in BWS gmw secrets.

Verified: typecheck + lint clean, 216/216 tests pass, live probe
'gubernur jawa barat' returned 3 mapped results
2026-09-14 00:44:38 +07:00
asepharyana 9685fa02be perf(gateway): retry transient attachment + wikipedia failures
- attachmentUploader: downloadDiscordAttachment retries CDN timeouts
  via retryWithBackoff (ATTACHMENT_RETRY_ATTEMPTS, 1s-8s backoff);
  AbortError normalized so 403/404 refresh path never fires on timeouts
- attachmentUploader: uploadAttachmentToTele uses ATTACHMENT_RETRY_ATTEMPTS
  instead of retries:0 (Tele 5xx under load was failing outright)
- wikipediaClient: wikipediaSummary retries once on abort/timeout
  (100% of prod summary errors were aborts); termGlossary drops its
  redundant second call (was up to 4 reqs/term under miss+retry)

Verified: typecheck + lint clean, 210/210 tests pass
2026-09-14 00:13:56 +07:00
asepharyana 43f2f8449d feat(docs): Add comprehensive agent guides and templates for spec-driven development 2026-09-11 18:56:28 +07:00
asepharyana 023217b260 feat(moderation): skip AI analysis for music bots via AI_SKIP_ANALYSIS_USER_IDS
Jockie Music (user 411916947773587456) posts now-playing embeds/spotify links
~1347 captured messages — every one consumed a moderation LLM call for zero
signal and contributed to batch timeouts. Config AI_SKIP_ANALYSIS_USER_IDS
(default=Jockie) skips them at ALL three analysis paths:
- queueMessageAnalysis entry (direct skip-result like age-restricted)
- batchScheduler processing (pre-batch filter)
- individual recovery path (no fallback spam for already-skipped authors)

Skip-result mirrors age_restricted: status=clean, flags=[skip_analysis_user],
action=none — stays visible in the dashboard, never analyzed.
2026-09-09 23:38:32 +07:00
asepharyana db4e84f057 fix(moderation): text batch timeout 45s→75s — router text model regularly exceeds 45s
The text model behind omniroute/9router consistently takes >45s on long-context
batches. At 45s every such batch fell through to the individual-fallback
queue which re-runs with its own timeout, then exhausted to ai_status=error.
75s keeps the bounded budget while letting the first-pass batch succeed.
2026-09-09 21:38:16 +07:00
asepharyana f85af3952a fix(moderation): audit fixes — media/vision timeout 120s, Qdrant retry w/ backoff, JSON repair in LLM caller
- AI_LLM_MEDIA_ANALYSIS_TIMEOUT_MS 60s→120s + vision 60s→120s: vision model
  via router regularly exceeded 60s, dropping media batches into the
  individual-fallback chain then exhausting into ai_status=error.
- Qdrant upserts: retryWithRetry() wraps PUT /points with exponential
  backoff (3 attempts, jitter) for transient 408/abort/ECONNRESET — the
  41 six-hour 'Qdrant upsert failed — semantic entry skipped' warnings were
  single-hop timeouts on a healthy-but-loaded Qdrant.
- LLM caller: on parse failure, attempt extractJson() structural repair of
  the raw content (models with thinking disabled sometimes emit JSON as
  plain text) before giving up and re-requesting.
2026-09-09 21:29:01 +07:00
dependabot[bot] de0d0bb85e build(deps): bump the production group across 1 directory with 3 updates
Bumps the production group with 3 updates in the /services/discord-gateway directory: [openai](https://github.com/openai/openai-node), [p-limit](https://github.com/sindresorhus/p-limit) and [p-retry](https://github.com/sindresorhus/p-retry).


Updates `openai` from 7.8.0 to 7.10.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-node/compare/v7.8.0...v7.10.0)

Updates `p-limit` from 7.3.1 to 7.3.2
- [Release notes](https://github.com/sindresorhus/p-limit/releases)
- [Commits](https://github.com/sindresorhus/p-limit/compare/v7.3.1...v7.3.2)

Updates `p-retry` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/sindresorhus/p-retry/releases)
- [Commits](https://github.com/sindresorhus/p-retry/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: openai
  dependency-version: 7.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: p-limit
  dependency-version: 7.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: p-retry
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-07 14:28:34 +00:00
asepharyana 276062fdd4 feat(ai): scope persistence, parallel tools, Prometheus token/cache counters
C2 full scope persistence:
- getRecentConversationContext now returns per-turn guildId/channelId
- processMessage merges historical scope when current request is unscoped
- chatbot remembers server context across all 8 history exchanges (not just 3)

A4+Metrics:
- Add incrementCounterBy(name, delta, labels) to gateway-metrics
- Token-usage counters: llm_tokens_total{model, type, label} per batch
- Cache hit counters: moderation_cache_hits{type} exact/semantic-qdrant/semantic-pg
- Cache miss counters: moderation_cache_misses per batch
- Prometheus /metrics now exposes cost + cache hit-rate for dashboards

Performance:
- Parallel tool execution within each chatbot round (Promise.all)
- All tool results collected before sending to model (ordering preserved)
- Tool failure now logged with structured warning (chatbot.tools.ts)

Verified: backend tsc+biome 37/37, discord-gateway tsc+biome 210/210
2026-09-04 15:32:03 +07:00
asepharyana 85204ca6f0 fix(moderation): enforcement safety net — username-only offense never auto-deleted
Even with the prompt firewall (username vs content), the LLM can still
occasionally mis-apply a content-level zero-tolerance flag (sara /
conflict_instigation) to a message whose ONLY violation is the username
(e.g. 'matikanetanyahu'). The auto-delete eligibility check only recognized
exact offensive_username flags, so such false positives still deleted the
message.

Add a belt-and-suspenders guard in isNicknameOnlyViolation: if the flag set
is entirely username-attributable (offensive_username/sara/conflict_instigation)
AND the analysis text corroborates that the violation is username-only with
clean message content, route to nickname-reset instead of message deletion.

Adds 6 test cases covering the real matikanetanyahu scenario and the
false-positive/negative boundaries.
2026-09-03 20:39:17 +07:00
asepharyana 68fbaa631a fix(moderation): prevent username-only violations from triggering content-level zero tolerance
Add explicit FIREWALL PENILAIAN rule separating username assessment from
message content assessment. Username containing political/religious terms
(matikanetanyahu etc) is assessed as offensive_username with severity low
— never triggers sara/conflict_instigation zero tolerance for content.

Changes:
- rules.ts: Add FIREWALL section before LARANGAN BERAT; clarify each
  zero-tolerance rule applies to ISI PESAN only; update hierarchy #9
- examples.ts: Fix example #11 status flagged→warn for clean username;
  add example #11b with matikanetanyahu case
- output.ts: Explicit status/action mapping for username-only violations
2026-09-03 20:05:37 +07:00
mytheclipsebotreview b761de8b98 Merge branch 'main' into dependabot/npm_and_yarn/services/discord-gateway/development-baee414eea 2026-09-02 21:42:31 +07:00
dependabot[bot] 53dfd3be41 build(deps-dev): bump tsx
Bumps the development group in /services/discord-gateway with 1 update: [tsx](https://github.com/privatenumber/tsx).


Updates `tsx` from 4.23.12 to 4.23.13
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.23.12...v4.23.13)

---
updated-dependencies:
- dependency-name: tsx
  dependency-version: 4.23.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 14:26:03 +00:00
dependabot[bot] 1cd17f53d5 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.5.2 to 4.5.4
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.5.2...v4.5.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 14:25:49 +00:00
asepharyana 955da396c7 debug(stream-watch): add per-packet decrypt diagnostics — why VIDEO-PKT fires but no mp4? 2026-09-02 19:24:25 +07:00
asepharyana ccfdbc860e revert(gateway): source defaults kembali ke imrnes (100.121.180.82) — outage usai 2026-09-02 16:02:29 +07:00
asepharyana e6aa9af283 fix(gateway): make moderation score optional — LLM omits it in media batches
result.score was required by zod; the LLM (gemini-3.5-flash-lite via 9router)
occasionally omits it for media batches, hard-failing the whole batch parse
('Zod validation failed: expected number, received undefined' at
results[0].score). Callers already null-coalesce (result.score ?? 0) and the
parser clampScore()s it, so requiring it only caused parse failures.
Adds regression tests: media-batch without score parses (score->0), and
score-present responses still parse with the value.
2026-09-02 13:15:05 +07:00
asepharyana 4c38d53972 fix(gateway): local infra defaults + qdrant collection retry-on-failure
- AI_LLM_BASE_URL default -> http://127.0.0.1:4014/v1 (was imrnes :20128/api/v1)
- QDRANT_URL fallback -> http://127.0.0.1:6333 (was imrnes :6333)
- ensureQdrantCollection: reset memoised promise on failure so a mid-way
  recreate abort (DELETE done, PUT failed) does not leave the collection
  permanently missing until process restart
- tests: qdrantEnsure.test.ts (3 cases: retry-on-failure, recreate, idempotent)
2026-09-02 12:57:54 +07:00
asepharyana e5304fde29 feat(gateway): add manual video-watch command for selfbot screen-share capture
A selfbot (user token) cannot auto-detect other members' camera/share
(no VOICE_STATE_UPDATE for others, 403 on member fetch). The only
selfbot-viable path to capture another member's SCREEN SHARE is an
operator-initiated STREAM_WATCH (gateway op 20, not gated on bot-vs-user).

Add video:watch / video:unwatch Redis commands routed via the existing
command handler to startStreamWatch/stopStreamWatch, which then does the
DAVE handshake + per-burst MP4 segmentation + DB insert + Tele upload
(already implemented in streamWatchReceiver).

- new VideoHandler (command-handler/video.handler.ts)
- register video:watch / video:unwatch in handler-registry + CommandHandler
- command constants COMMAND_VIDEO_WATCH / COMMAND_VIDEO_UNWATCH
- resolve active voice channel from voice controller + client cache
- 8 unit tests (videoHandler.test.ts)
- biome fixes for pre-existing test import ordering

All green: typecheck, build, lint (174 files), 200 tests.
2026-09-02 10:12:03 +07:00
asepharyana 43594af3c8 fix: CI biome errors + enhanced GUILD_CREATE/READY voice_states diagnostic
- live-speaker.ts: unused var [id] in clearAllSpeakers → [_]
- migrate.ts: useTemplate string concat → template literal
- streamWatchReceiver: remove unused watchKey param from closeCurrentSegment
- videoRecorder: log all raw WS event types + READY sessions.voice + broadcaster_user_ids
2026-09-02 02:31:32 +07:00
asepharyana fdcd53d149 debug(video): enhanced diag — READY sessions.voice + broadcaster_user_ids + all raw types
Dump all WS raw event types after 10s (see if GUILD_CREATE exists at all),
and log broadcaster_user_ids + sessions.voice from READY payload.
Selfbot may RESUME (skip GUILD_CREATE) — voice data may only be in READY.
2026-09-02 02:26:26 +07:00
asepharyana bc66babd45 debug(video): log raw GUILD_CREATE/READY/GUILD_MEMBERS_CHUNK shape
Temporary diagnostic to see whether GUILD_CREATE.voice_states actually
reaches the selfbot raw listener (selfbot-v13 emits everything via
WebSocketShard Events.RAW). Will remove once root cause confirmed.
2026-09-02 02:10:22 +07:00
asepharyana ac32179bb1 fix(video): detect pre-existing voice users from GUILD_CREATE.voice_states
Real root cause of 'tidak mendeteksi user yg sudah ada di voice':

The selfbot's discord.js-selfbot-v13 GUILD_CREATE handler only sends
GUILD_SUBSCRIPTIONS_BULK — it DROPS d.voice_states from the payload.
So when the gateway (re)starts, every user who was ALREADY in the voice
channel (with camera or screen-share on) is invisible: channel.members is
empty, and REST fallbacks don't work for user tokens (verified live:
GET /channels/{id}/voice-states -> 404, GET /guilds/{id}/members -> 403).

Fix: register our own raw listener for GUILD_CREATE, capture
d.voice_states per guild (buffer it), and consume it in
scanExistingStreamers when the channel gets tracked (voice join happens
after GUILD_CREATE). This is the ONLY user-token-compatible source of
'who is in voice with video right now'.

- videoRecorder: +pendingGuildCreateVoiceStates buffer, +raw GUILD_CREATE
  listener, Path C consumes buffered states (self_video/self_stream,
  matching channel, skip self) before the REST fallback
- scanExistingStreamers: Path A cache -> Path C GUILD_CREATE -> Path D
  REST members.fetch() best-effort
- +1 test: GUILD_CREATE voice_states buffer -> watch camera+screen-share,
  skip self/no-video/other-channel (192/192 pass)
- typecheck + build + biome clean (1 pre-existing warning)
2026-09-02 02:03:31 +07:00
asepharyana cee33c0d1f fix(video): detect pre-existing voice members after gateway restart
Root cause: scanExistingStreamers only read channel.members, which is
EMPTY after a gateway restart because the selfbot's guild member cache
hasn't been populated yet. So any user who was ALREADY on camera /
screen-sharing when the bot (re)joined was never detected → no video.

Fix: when the member cache is empty, fall back to guild.members.fetch()
(REST GET /guilds/{id}/members — user-token compatible; the bot-only
GET /channels/{id}/voice-states returns 404 for selfbots, verified live)
which populates member.voice states, then re-scan channel.members for
streaming/selfVideo.

- scanExistingStreamers is now async; trackChannel fire-and-forgets it
- logs source=cache vs source=rest-members for observability
- +1 test: cold-start channel.members empty → REST fetch → watch camera user
- 191/191 tests pass, typecheck + build + biome clean
2026-09-02 01:13:50 +07:00
asepharyana dd9f2f6bbc fix(voice): self-undeafen/self-unmute bot instantly on server mute/deafen
Previously forceSelfServerUnmuteUndeafen only ran on video-watch attempts and
after voice reconnects — so when an admin server-muted or server-deafened the
bot, it stayed muted/deafened for minutes (or forever if no streamer came on).

Add registerSelfVoiceStateGuard: a voiceStateUpdate listener that detects the
bot's own serverMute/serverDeaf transition to true and immediately re-issues
mute:false,deaf:false. Wired at client-ready in bootstrap.ts. Best-effort,
idempotent, never blocks the gateway.
2026-09-02 00:30:57 +07:00
asepharyana 7122258d7c fix(voice): read SSRCMap internal map via 'map' (not '_map')
videoReceiver.getSsrcInternalMap read asAny._map, but @discordjs/voice
0.19.x exposes the SSRC map as the public field 'map'. So inferVideoOwner
(e.g. matching a video RTP SSRC to a user via audio-SSRC proximity) always
returned null -> every video packet was silently dropped after decryption.
Accept both 'map' and '_map'. Unblocks camera/screen-share attribute when
op12 does not carry a videoSSRC stream entry.
2026-09-02 00:10:02 +07:00
asepharyana 7c21629404 fix(voice): DAVE video decrypt fallback chain (VIDEO→AUDIO→passthrough)
streamWatchReceiver: DAVE decrypt for screen-share video packets was
returning null every time (VIDEO-PKT diag showed packets arriving but
no 'Video segment opened'). Three possible causes:
1. No VIDEO decryptor in MLS group (audio-only handshake)
2. GoLive stream tags video packets as AUDIO
3. Screen-share payloads are unencrypted above the AES layer

Fix: try MediaType.VIDEO first, then MediaType.AUDIO, then passthrough
(legacy-decrypted payload as-is). This covers all three modes without
breaking audio recording.
2026-09-01 23:54:07 +07:00
asepharyana 7bfdf9c85c feat(voice): fork @discordjs/voice with video receive support
Fork @discordjs/voice 0.19.2 into vendor/discord-voice-fork and patch the
voice gateway handshake so Discord sends camera/screen-share RTP video:

- Identify payload now declares video:true + streams:[] (derived from
  Discord-RE/Discord-video-stream) - this is what makes Discord deliver
  H264 (PT 96-127) to the voice socket. Previously the client never
  declared video capability, so Discord omitted all video SSRCs/packets.
- op-12 Speaking handler maps streams[].ssrc -> videoSSRC alongside the
  audio SSRC, so ssrcMap carries camera/screen-share stream IDs.
- SSRCMap.get() now also resolves video SSRCs (video RTP arrives on a
  different SSRC than audio), enabling attribution for videoReceiver.ts.
- Both dist/index.js (CJS) and dist/index.mjs (ESM) patched; 3 isolated
  hunks vs upstream, verified by diff.
- package.json points @discordjs/voice -> file:vendor/discord-voice-fork
  (pnpm lockfile updated, CI --frozen-lockfile compatible).
- .gitignore: replace global dist/ with per-service explicit patterns so
  the vendored fork's dist/ is committed while build outputs stay ignored.
- tests: +3 SSRCMap videoSSRC cases (190 total, all pass).

The receive-side pipeline (H264Depacketizer -> .h264 -> muxToMp4 -> MP4)
already exists in videoReceiver.ts; this unblocks it by making Discord
actually deliver video packets.
2026-09-01 23:35:13 +07:00
asepharyana 1367a2257f fix(gateway): detect camera-only video (selfVideo) for stream watch
Previously only member.voice.streaming (screen share, self_stream) triggered
video capture. Discord reports camera via self_video:true, so camera-only
users were never watched. Now scanExistingStreamers and handleVoiceStateUpdate
start a watch when streaming OR selfVideo is set, and stop it when both clear.
2026-09-01 22:26:38 +07:00
asepharyana 4ec9685194 feat(gateway): split video recording into silence-based segments like voice
Video (camera + screen share) DAVE stream-watch now produces per-burst
MP4 segments instead of one long .h264 per watch:
- Detects VIDEO_SILENCE_MS (4000ms) of no H264 packets → closes the
  current segment, muxes to MP4, registers in voice_recordings + uploads
  to TeleUploader, then reopens for the next burst (mirrors voice AfterSilence).
- Per-watch segment counter + per-segment depacketizer reset + closing
  guard + write-error swallow so races (silence close vs in-flight UDP
  packet) never corrupt files or crash the gateway.
- Frontend: recordings deck renders a native <video> player for MP4 rows
  (detected by filename), keeps single-playback registry across audio+video.
2026-09-01 21:51:44 +07:00
dependabot[bot] 1f196d2267 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.4.3 to 4.5.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.4.3...v4.5.2)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 14:33:45 +00:00
asepharyana fa72fe03cd feat(archive): show real channel/thread names in semantic search UI
Gateway archive embedder now parses metadata.channel.{channelName,threadName}
from each message and stores channel_name/thread_name in the Qdrant payload.
Backend exposes them; the semantic results card renders the thread name (or
channel name) instead of a raw #snowflake, with the ID as a last-resort
fallback for legacy points. Matches the message feed's channel-label logic
(getMessageChannelLabel).
2026-09-01 19:27:56 +07:00
asepharyana 26a690943d feat(archive): rich metadata in semantic search — username/channel/guild context + guild filter
Archive payload now stores username, channel_id, guild_id, thread_id and the
real message created_at (not embed time). Backend searchArray accepts an
optional guildId and applies a Qdrant payload filter so results can be scoped
to the guild being viewed. API/frontend expose the new fields and the
semantic results card shows who said it, in which channel, and when —
turning bare text blobs into contextual results. Old points fall back to
analyzed_at and omit the new fields gracefully.
2026-09-01 18:56:14 +07:00
asepharyana c704fbf7a5 fix(gateway): make voice transcription model configurable + router-compatible
- AI_VOICE_TRANSCRIPTION_MODEL config (default whisper-1) so the model can be a provider-qualified id (openrouter/openai/whisper-1) that actually has credentials through 9router/omniroute — bare whisper-1 maps to the openai provider which has none
- response_format json (not text): 9router proxies only json/verbose_json transcription responses; text returns 400
- parse text from the json response object
- prod env updated: model=openrouter/openai/whisper-1 (still needs OpenRouter STT balance — 402 until funded)
2026-09-01 18:25:14 +07:00
asepharyana 7ef86c81ca feat(ai): audit + harden embedding pipeline
- Normalize text before embedding (strip mentions/URLs/emoji/markdown/control chars, lowercase, truncate) on both write and query sides so vectors aren't diluted and tokens aren't wasted
- embeddingClient: retry embeddings (maxRetries 2), validate batch dimension consistency, preserve index alignment for empty-normalized texts
- archiveEmbedder: store normalized text in archive payload, skip empty-normalized content
- backend: normalize search queries, make archive search similarity threshold configurable (AI_LLM_EMBEDDING_ARCHIVE_MIN_SIMILARITY, default 0.6)
2026-09-01 18:01:47 +07:00
asepharyana 0e31aa06b8 feat(gateway): refactor term extraction and scoring logic into textSignals.ts for reuse 2026-08-31 22:59:27 +07:00
asepharyana 12cc956329 feat(gateway): implement separate Piscina pools for text and media analysis to optimize processing 2026-08-31 22:59:27 +07:00
dependabot[bot] a0794a67d1 build(deps): bump the production group across 1 directory with 4 updates
Bumps the production group with 4 updates in the /services/discord-gateway directory: [axios](https://github.com/axios/axios), [openai](https://github.com/openai/openai-node), [piscina](https://github.com/piscinajs/piscina) and [sharp](https://github.com/lovell/sharp).


Updates `axios` from 1.19.0 to 1.20.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.19.0...v1.20.0)

Updates `openai` from 7.5.0 to 7.8.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-node/compare/v7.5.0...v7.8.0)

Updates `piscina` from 5.3.1 to 5.3.2
- [Release notes](https://github.com/piscinajs/piscina/releases)
- [Changelog](https://github.com/piscinajs/piscina/blob/v5.3.2/CHANGELOG.md)
- [Commits](https://github.com/piscinajs/piscina/compare/v5.3.1...v5.3.2)

Updates `sharp` from 0.35.3 to 0.35.4
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.3...v0.35.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: openai
  dependency-version: 7.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: piscina
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: sharp
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:44:40 +00:00
dependabot[bot] cb36a2fbb0 build(deps-dev): bump the development group
Bumps the development group in /services/discord-gateway with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@biomejs/biome` from 2.5.10 to 2.5.11
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.11/packages/@biomejs/biome)

Updates `@types/node` from 26.3.0 to 26.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: "@types/node"
  dependency-version: 26.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:30:00 +00:00
asepharyana 2b6a1eca19 fix(gateway): re-assert server-undeafen+unmute before every video watch
The bot's own VOICE_STATE_UPDATE showed server-level deaf:true — a
server-deafened member is NOT sent the streamer's audiovisual RTP by Discord,
which is the likely reason no H264 arrives despite the DAVE watch reaching
Ready. The previous fire-and-forget forceSelfServerUnmuteUndeafen() ran once
after the first Ready join and silently reverted on reconnect/restart.

- Export forceSelfServerUnmuteUndeafen from recorder.ts; re-assert it (with
  read-back verification logging stillDeaf) at the START of every
  startStreamWatch() before STREAM_WATCH is sent (dynamic import avoids the
  recorder <-> videoRecorder <-> streamWatchReceiver module cycle).
- Re-assert it again after a successful voice reconnect.
- startStreamWatch() is now async; callers use void.
2026-08-31 19:16:56 +07:00
asepharyana 9606187861 feat(gateway): hexdump+ssrc of watch UDP packets
maxLen stayed 72 across 243 packets (no real H264, which is hundreds+ bytes) —
only 44-72-byte RTP packets on PT 76/72/73 arrive. Add ssrc + first-32-bytes
hex so we can identify exactly what Discord sends to the watch socket (control
packets vs stale video), which determines whether the gap is upstream routing
or whether large H264 packets are missing entirely.
2026-08-31 16:47:27 +07:00
asepharyana b423d21b23 feat(gateway): aggregate VIDEO-PKT diag — distinct PTs + maxLen
Enhance watch-socket diagnostic to report distinct RTP payload types seen and
the max packet length, so we can distinguish 'only small control packets arrive
(no real H264)' from 'H264 arrives but decrypt fails'. Live already confirmed
dave=true ready=true with packets flowing but no burst — need to know if they're
tiny 52-byte control packets (PT 73) or large H264.
2026-08-31 16:40:36 +07:00